Bazel Kod Tabanı

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.

Yolculuğun ortasında olan kullanıcıların, kaybolan basit bir yolda kendilerini ormanın içinde bulmamaları için, bu belgede çalışmaya başlamayı kolaylaştırmak amacıyla kod tabanıyla ilgili bir genel bakış sağlamaya çalışılır.

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

Katkılar, normal GitHub pull isteği mekanizması üzerinden kabul edilir, bir Google çalışanı tarafından manuel olarak dahili kaynak ağacına içe aktarılır, ardından tekrar GitHub'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, Bazel'in derlemeler arasında durumu korumasına olanak tanır.

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

  1. Dosyanın daha önce ayıklanıp ayıklanmadığını kontrol eder. Aksi takdirde, bu işlemi yapar. 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 komutunu başka bir komutla paralel olarak çalıştıramamak biraz utanç verici olduğundan birden fazla komutu paralel olarak çalıştırmak için bazı altyapı vardır. Ana engelleyici, BlazeModule'lerin yaşam döngüsü ve BlazeRuntime içindeki bir durumdur.

Bazel sunucusu, bir komutun sonunda istemcinin döndürmesi gereken çıkış kodunu iletir. İlginç bir kırışıklık ise bazel run'nin uygulanmasıdır: Bu komutun işi Bazel'ın yeni oluşturduğu bir şeyi çalıştırmaktır ancak bir terminali olmadığından sunucu işleminden bunu yapamaz. Bunun yerine, müşteriye hangi ikili programı ujexec() işlevini ve hangi bağımsız değişkenlerle birlikte çalıştıracağını 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üzeni bölümünde 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 "çıkış kullanıcısı kökü" altına yerleştirir. Bu değer genellikle $HOME/.cache/bazel/_bazel_${USER} değerine sahiptir 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. Genellikle $OUTPUT_USER_ROOT/<checksum of the path to the workspace>'te. --output_base başlangıç seçeneği kullanılarak değiştirilebilir. Bu seçenek, diğer özelliklerinin yanı sıra herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalışabileceği sınırlamayı aşmak için de yararlıdır.

Çıkış dizininde şunlar bulunur:

  • $OUTPUT_BASE/external adresindeki getirilen harici depolar.
  • Geçerli 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. Uyumsuz bir değişiklik olduğu için 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 işlemi

Bazel sunucusu kontrolü ele alıp yürütmesi gereken komut hakkında bilgi aldıktan sonra, sırasıyla aşağıdaki olaylar 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 bulunur. 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 yolu oluşturulur. Etkinlik veri yolu, derleme sırasında gerçekleşen etkinliklerin akışıdı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. Kontrol, komuta geçer. 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ı başlar. 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

Bazel çağrısı için komut satırı seçenekleri, OptionsParsingResult nesnesinde açıklanmıştır. Bu nesne de "seçenek sınıflarından" seçeneklerin değerlerine bir harita içerir. "Seçenek sınıfı", OptionsBase öğesinin bir alt sınıfıdır ve birbiriyle alakalı 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ı ve sonunda bir 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 (Java'da RuleContext.getFragment() veya Starlark'ta ctx.fragments aracılığıyla) kullanılmak üzere tasarlanmıştır. Bunlardan bazıları (örneğin, C++ yapılıp yapılmayacağına tarama yapılıp yapılmayacağına bakılmaksızın) yürütme aşamasında okunur. Ancak BuildConfiguration o sırada kullanılamadığı için her zaman açık bir tesisat kurulumu gerekir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.

UYARI: OptionsBase örneklerinin sabit olduğunu varsaymayı ve bunları bu şekilde kullanmayı (SkyKeys gibi bir özellikte) kullanmayı seviyoruz. Böyle bir durum söz konusu değildir. Bunların değiştirilmesi, Bazel'ı hata ayıklaması zor şekillerde bozmanın gerçekten iyi bir yoludur. Maalesef bu öğeleri gerçekten değiştirilemez hale getirmek büyük bir çaba gerektiriyor. (Yapım aşamasından hemen sonra bir FragmentOptions üzerinde herhangi bir değişiklik yapmadan önce ve equals() veya hashCode() çağrılmadan önce değişiklik yapılması sorun yaratmaz.)

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

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

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'in gördüğü şekliyle kaynak ağaç

Bazel, kaynak kodu okuyup yorumlayarak yazılım oluşturma işiyle uğraşır. 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, derlemeyi çalıştırmak için kullanılan tüm kaynak kodunu içeren tek bir kaynak ağaç olan bir monorepo üzerinde çalıştırılıyordu. Buna karşın Bazel, kaynak kodu birden fazla depoya yayılan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğerlerine ise "harici depo" 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, yani biri diğer Starlark dosyalarını load(). 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, harici depoları ise $EXECROOT/external veya $EXECROOT/.. ile sembolik olarak bağlayan 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. İkisi de mevcutsa Bazel BUILD.bazel dosyasını tercih eder. BUILD dosyanın hâlâ kabul edilmesinin nedeni, Bazel'in üst öğesi olan Blaze'in 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 uygulandı, Starlark çevirmenini çağırarak çalışır ve mevcut kural sınıfları kümesi hakkında bilgi sahibi olmayı gerektirir. Paket yüklemenin sonucu bir Package nesnesi olur. Çoğunlukla bir dizeyi (hedefin adı) hedefin kendisine eşleyen bir eşlemedir.

Paket yükleme sırasındaki karmaşıklığın büyük bir kısmı "globbing" işlemidir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez. Bunun yerine, glob'ları (glob(["**/*.java"]) gibi) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere inen (alt paketlere değil) yinelemeli glob'ları destekler. Bu, dosya sistemine erişim gerektirir. Bu yavaş olabileceğinden, sistemin paralel ve mümkün olduğunca 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'i kullanan ve "Skyframe yeniden başlatılması" sorunundan kaçınmak için eski globber'a geri dönen bir sürüm (aşağıda açıklanmıştır)

Package sınıfının kendisi, özel olarak WORKSPACE dosyasını ayrıştırmak için kullanılan ve gerçek paketler için bir anlam ifade etmeyen bazı üyeler içerir. Normal paketleri tanımlayan nesnelerin başka bir şeyi açıklayan alanlar içermemesi gerektiğinden bu bir tasarım hatasıdır. 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ürlerde hedeflerden oluşur:

  1. Dosyalar: Derlemenin girdisi veya çıkışı olan öğeler. Bazel dilinde bunlara eser (başka bir yerde tartışılan) diyoruz. Derleme sırasında oluşturulan tüm dosyalar hedef değildir. Bazel çıktılarının ilişkili bir etiketinin olmaması yaygın bir durumdur.
  2. Kurallar: Bu kurallar, çıktıları girişlerden elde etme adımlarını 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 tartışılır.

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

  1. Depo atlanırsa etiket ana depoya alınır.
  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 tür kurala ("C++ kitaplığı" gibi) "kural sınıfı" denir. Kural sınıfları Starlark'ta (rule() işlevi) veya Java'da ("yerel kurallar" olarak adlandırılır, RuleClass türü) uygulanabilir. Uzun vadede, dile özgü her kural Starlark'ta uygulanacaktır, ancak bazı eski kural aileleri (Java veya C++ gibi) şimdilik hâlâ 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 (srcs, deps gibi): türleri, varsayılan değerleri, kısıtlamaları vb.
  2. Varsa her bir özelliğe ekli yapılandırma geçişleri ve unsurlar
  3. Kuralın uygulanması
  4. Kuralın "genellikle" oluşturduğu geçişli bilgi sağlayıcılar

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

Skyframe

Bazel'in temelini oluşturan değerlendirme çerçevesi Skyframe'dir. Modeli, bir derleme sırasında oluşturulması gereken her şeyin, veri parçalarından bağımlılıklarına (yani, oluşturulması için bilinmesi gereken diğer veri parçalarına) yönlendiren kenarları olan yönlendirilmiş bir döngüsel olmayan grafikte düzenlenmesidir.

Grafikteki düğümlere SkyValue, adlarına ise SkyKey denir. Her ikisi de tamamen sabittir; bu kaynaklardan yalnızca sabit nesnelere erişilebilir. Bu değişmezlik neredeyse her zaman geçerlidir ve geçerli olmadığı durumlarda (ör. BuildConfigurationValue ve SkyKey üyesi olan bağımsız seçenek sınıfları BuildOptions için) bunları değiştirmemeye veya yalnızca dışarıdan gözlemlenemeyen şekillerde değiştirmeye çalışıyoruz. 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 bazel dump --skyframe=detailed komutunu çalıştırmaktır. Bu işlem, grafiğin dökümünü her satıra bir SkyValue olacak şekilde atar. 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, söz konusu bağımlılıkları Skyframe'in dahili grafiğine kaydetmenin yan etkisine sahiptir ve böylece Skyframe, bağımlılıklarından biri değiştiğinde fonksiyonu yeniden değerlendireceğini bilir. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması, SkyFunction ve SkyValue ayrıntı düzeyinde çalışır.

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 sonucunda, yeniden başlatmadan önce SkyFunction içinde gerçekleştirilen tüm hesaplamaların tekrarlanması gerekir. Ancak önbelleğe alınan SkyValues bağımlılığını değerlendirmek için yapılan çalışmalar buna dahil değildir. Bu nedenle, genellikle aşağıdakileri yaparak bu sorunu gideririz:

  1. Yeniden başlatma sayısını sınırlandırmak için bağımlılıkları toplu olarak 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ğu için bu işlem stratejik olarak yapılmalıdır.
  3. SkyFunction.Environment.getState() kullanarak veya "Skyframe'in arkasında" geçici bir statik önbellek tutarak 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. Bu, bazı (ancak hepsini değil) kullanıcıları dildeki genel programlama görevlerini tamamlamaya çalışmaktan caydıran bir Turing-complete değildir.

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

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

  1. BUILD dili. Burada yeni kurallar tanımlanır. Bu bağlamda çalışan Starlark kodu yalnızca BUILD dosyasının ve bu dosya tarafından yüklenen .bzl dosyalarının içeriğine 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 konu hakkında daha fazla bilgiyi aşağıda bulabilirsiniz).
  3. WORKSPACE dosyası. Harici depolar (ana kaynak ağacında olmayan kod) burada tanımlanır.
  4. Depo kural tanımları. Bu aşamada yeni harici depo türleri 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 erişebilir.

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 bilgiyi burada bulabilirsiniz.

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.

Daha önce serileştirilmiş olan iki farklı bölüme ayrılabildiği için bu aşamaya "yükleme/analiz aşaması" denir; ancak bu aşamalar artık zaman içinde çakışabilir:

  1. Paketleri yükleme, yani BUILD dosyalarını bunları temsil eden Package nesnelere 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 yapılandırılmış her hedef, aşağıdan yukarıya analiz edilmelidir. Başka bir deyişle, önce yaprak düğümleri, ardından komut satırındakilere kadar analiz edilmelidir. Tek bir yapılandırılmış hedefin analizine yönelik girişler şunlardır:

  1. Yapılandırma. ("how" 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. 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ılan 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 hedefin analizinden elde edilen çıkış:

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

Java kurallarına sunulan API RuleContext, Starlark kurallarının ctx bağımsız değişkenine eşdeğerdir. API'si daha güçlüdür, ancak aynı zamanda Bad ThingsTM yapmak daha kolaydır. Örneğin, zamanı veya alan karmaşıklığı ikinci dereceden (veya daha kötüsü) olan bir kod yazmak, Bazel sunucusunun bir Java istisnası ile çökertilmesi veya sabit değerleri ihlal etmek (ör. bir Options örneğini yanlışlıkla değiştirmek ya da yapılandırılmış bir hedefi değişken yapmak suretiyle) gibi işlemler için kullanabilirsiniz.

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

Yapılandırmalar

Yapılandırmalar bir hedef oluşturma "nasıl"dır? Örneğin, 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 ve hedef kod için çalıştırılan bir araç için aynı kod kullanıldığında ve çapraz derleme yaparken veya bir fat Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren bir uygulama) geliştirdiğimizde faydalıdır.

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

Bu durum, örneğin istenen test çalıştırmalarının sayısı değişse bile yalnızca test hedeflerini etkilemesine rağmen derlemenin tamamını yeniden analiz etmeniz gerekmesi gibi anormalliklere neden olur (Bunun olmaması için yapılandırmaları "kırpmayı" planlıyoruz ancak bu henüz hazır değil).

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 hataları (ör. Java parçasını kullanan Python kuralları) önlemek hem de yapılandırmayı kısaltmayı kolaylaştırmaktı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ı değildir. 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 uçta. Bunlar RuleClass.Builder.cfg() içinde belirtilir.

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

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

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

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ş bilgisi sağlayıcıları

Geçiş bilgisi sağlayıcıları, yapılandırılmış hedeflere bağlı diğer yapılandırılmış hedefler hakkında bilgi vermenin bir yoludur (ve _only _way). Adında "geçişli" olmasının nedeni, bunun genellikle bir tür yapılandırılmış hedefin geçişli kapanmasının bir tür toplamı olmasıdır.

Java geçişli bilgi sağlayıcıları ile Starlark bilgi sağlayıcıları arasında genellikle 1:1 bir ilişki vardır (DefaultInfo hariç. Bu API, Java bilgi sağlayıcısının doğrudan transliterasyonundan daha Starlark tarzında olduğu için FileProvider, FilesToRunProvider ve RunfilesProvider'ın bir birleşimidir). Anahtarları aşağıdakilerden biridir:

  1. Java Sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar tarafından kullanılabilir. Bu sağlayıcılar TransitiveInfoProvider alt sınıfıdır.
  2. Dize. Bu, eskidir ve ad çakışmasına açık olduğu için 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ı sembolü. Bu, provider() işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcı oluşturmak için önerilen yöntemdir. 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ıracak vaktimiz yoktu) ve TransitiveInfoProvider alt sınıfa Starlark'tan erişilemiyor.

Yapılandırılmış hedefler

Yapılandırılmış hedefler RuleConfiguredTargetFactory olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf bulunur. Starlark tarafından yapılandırılan hedefler StarlarkRuleConfiguredTargetUtil.buildRule() üzerinden oluşturulur.

Yapılandırılmış hedef fabrikalar, dönüş değerlerini oluşturmak için RuleConfiguredTargetBuilder kullanmalıdır. Bu rapor aşağıdakilerden oluşur:

  1. filesToBuild, "bu kuralın temsil ettiği dosya kümesi"ne ilişkin belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef komut satırında veya bir genrule'nin srcs bölümündeyken derlenen dosyalardır.
  2. Çalıştırma dosyaları, normal ve veriler.
  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ıştırma dosyaları

Bazı ikili programların çalışması için veri dosyaları gerekir. Bunun belirgin bir örneği, giriş dosyaları gerektiren testlerdir. 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, çalıştırma dosyaları ağacındaki bir dosyanın yolundan, dosyayı temsil eden Artifact örneğine kadar uzanan bir haritadır. İki nedenden dolayı tek bir Map parametresinden biraz daha karmaşıktır:

  • Bir dosyanın runfiles yolu çoğu zaman execpath ile aynıdır. Bunu, 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.

Çalışma dosyaları RunfilesProvider kullanılarak toplanır: Bu sınıfın bir örneği, yapılandırılmış bir hedefi (kitaplık gibi) ve bunun geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir küme gibi toplanır (aslında, kapak altındaki iç içe yerleştirilmiş kümeler kullanılarak uygulanırlar): Her hedef, bağımlılarının çalıştırma dosyalarını birleştirir, kendilerinden bir kısmını ekler ve daha sonra, elde edilen kurulumu bağımlılık grafiğinde 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 bazen bir veri özelliği aracılığıyla bağımlı olduğunda 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 Windows'ta bazen sembolik bağlantıları desteklemeyen çalışma dosyası 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, bunların tümünü temsil etmek üzere runfiles ara yöneticisi kullanılabilir.
  • Çalışma dosyalarını RunfilesSupport nesnesinin temsil ettiği ikili programı ç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. Bunlar, Bazel kullanıcıları için burada açıklanmıştır. Protokol arabellekleri iyi bir motive edici örnektir: proto_library kuralı herhangi 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'de de SkyValue olarak temsil edilir ve oluşturulma şekilleri, yapılandırılmış hedeflerin oluşturulma biçimlerine çok benzer: RuleContext'ye erişimi olan ConfiguredAspectFactory adında bir fabrika sınıfı vardır. Ancak yapılandırılmış hedef fabrikaların aksine, ekli olduğu yapılandırılmış hedef ve sağlayıcılarını da bilir.

Bağımlılık grafiğinde aşağı doğru dağıtılan yönler 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, özelliğin tanımıdır. Gerektiği sağlayıcıları ve sağladığı sağlayıcıları içerir ve uygulamanın uygulanması için uygun AspectClass örneği gibi bir referans içerir. RuleClass ile benzerdir.
  3. AspectParameters, bağımlılık grafiğinde aşağı doğru dağıtılan bir yönü parametreleştirmenin 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. En boy sınıfı, tanımı ve parametrelerinden oluşur.
  5. RuleAspect, belirli bir kuralın hangi yönlerinin dağıtılacağını belirleyen işlevdir. Bu, Rule -> Aspect fonksiyonudur.

Beklemediğimiz bir komplikasyon, yönlerin diğer yönlere bağlanabilmesidir. Örneğin, bir Java IDE'nin sınıf yolunu toplayan bir yön, muhtemelen sınıf yolundaki tüm .jar dosyaları hakkında bilgi edinmek ister ancak bunların bazıları protokol arabellekleridir. Bu durumda, IDE özelliği (proto_library kuralı + Java proto yönü) çiftine eklenmelidir.

Belirli yönlerin karmaşıklığını bu sınıfta ele alıyoruz AspectCollection.

Platformlar ve araç zincirleri

Bazel, derleme işlemlerinin çalıştırıldığı birden fazla mimarinin ve kodun derlendiği birden fazla mimarinin bulunabileceği derlemeleri, yani çoklu platform derlemelerini destekler. Bu mimarilere Bazel dilinde platform adı verilir (tam dokümanlar burada).

Platformlar, kısıtlama ayarlarından ("CPU mimarisi" kavramı gibi), kısıtlı değerlere (ör. x86_64 gibi belirli bir CPU) giden bir anahtar/değer eşlemesi ile tanımlanır. @platforms deposunda, en sık kullanılan kısıtlama ayarlarını ve değerlerini içeren bir "sözlüğü" bulabilirsiniz.

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).

Bunu yapmak için araç zincirlerine, destekledikleri yürütme ve hedef platform kısıtlamalarıyla not verilir. 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 toolchain() kuralı, araç zincirinin türünü (C++ veya Java gibi) belirtir (ikincisi, toolchain_type() kuralıyla temsil edilir).
  2. Gerçek araç zincirini tanımlayan dile özgü bir kural (cc_toolchain() gibi)

Bu işlem, araç zinciri çözümlemesi yapmak için her araç zincirinin kısıtlamalarını bilmemiz gerektiği ve dile özgü *_toolchain() kuralları bundan çok daha fazla bilgi içerdiği için 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

Mevcut yürütme platformları grubu RegisteredExecutionPlatformsFunction cinsinden hesaplanır .

Yapılandırılmış bir hedefin hedef platformu, PlatformOptions.computeTargetPlatform() ile belirlenir . İleride birden fazla hedef platformu desteklemek istediğimiz için bu bir platformlar listesidir, ancak henüz uygulanmamıştır.

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

  • Kayıtlı araç zincirleri grubu (WORKSPACE dosyasında ve yapılandırmada)
  • İstenen yürütme ve hedef platformlar (yapılandırmada)
  • Yapılandırılmış hedefin gerektirdiği araç zinciri türleri grubu (UnloadedToolchainContextKey) içinde
  • UnloadedToolchainContextKey iç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ç zincirlerinin kendisini değil, yalnızca etiketlerini içermesi nedeniyle "unload" olarak adlandırılmıştı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 geçiş yapmak için platform eşlemeleri uyguladık. Kodu PlatformMappingFunction dilindedir ve Starlark dışında bir "küçük dil" kullanmaktadır.

Sınırlamalar

Bazen kullanıcılar bir hedefi yalnızca birkaç platformla uyumlu olarak tanımlamak isteyebilir. Maalesef Bazel bu amaca ulaşmak için birden fazla mekanizmaya sahiptir:

  • 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ılmamaktadır.

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. En doğrudan kullanılan spesifikasyon biçimi budur. Kuralın bu grup için desteklediği ortam grubunu tam olarak belirtir.
  2. compatible_with= özelliği aracılığıyla. Bu şekilde, bir kuralın desteklediği, varsayılan olarak desteklenen "standart" ortamlara ek olarak ortamlar tanımlanır.
  3. Paket düzeyindeki default_restricted_to= ve default_compatible_with= özellikleri aracılığıyla
  4. environment_group() kurallarındaki varsayılan spesifikasyonlar 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. Ortam grubunun tanımı, bu ortamlardan hangilerinin restricted_to= / environment() özellikleriyle aksi belirtilmediği takdirde "varsayılan" tarafından desteklenmesi gerektiğini içerir. Bu tür özelliklere sahip olmayan bir kural, tüm varsayılanları devralır.
  5. Kural sınıfı varsayılanı aracılığıyla. Bu, belirli bir kural sınıfının tüm örnekleri için genel varsayılanları geçersiz kılar. Bu, örneğin, her ö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 ve her ortam grubunun varsayılan ortamlarını belirtmesi 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ünün uygulanması RuleContextConstraintSemantics ve TopLevelConstraintSemantics şeklindedir.

Platform kısıtlamaları

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

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'un yasası uyarınca kullanıcılar, uygulama ayrıntıları olarak kabul ettiğiniz davranışlara bağlı kalacak.

Bazel bunu görünürlük adı verilen mekanizmayla destekler: Belirli bir hedefe yalnızca görünürlük özelliğini kullanarak bağımlı olabileceğ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ıdır.)

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

  • RuleVisibility arayüzü, görünürlük bildirimini temsil eder. Sabit (tamamen herkese açık veya tamamen gizli) veya bir 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. Bu üçlünün mantığı, //pkg/... gibi tek bir kalıpa karşılık gelen PackageSpecification, tek bir package_group packages özelliğine karşılık gelen PackageGroupContents ve bir package_group ile geçişli includes üzerinde toplanan PackageSpecificationProvider aracılığıyla uygulanır.
  • Görünürlük etiketi listelerinden bağımlılıklara geçiş, DependencyResolver.visitTargetVisibility ve birkaç başka yerde yapılır.
  • Asıl kontrol CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility() içinde yapılır

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

Yapılandırılmış bir hedef, çoğu zaman bağımlılıklarından bir dizi dosya toplar, kendi dosyasını ekler ve toplu kümeyi, buna bağımlı olan yapılandırılmış hedeflerin aynısını yapabilmesi için bir geçişli bilgi sağlayıcıya sarmalar. Örnekler:

  • Derleme için kullanılan C++ başlık 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
  • Python kuralının geçişli kapanışındaki Python dosyaları kümesi

Örneğin, List veya Set gibi araçları kullanarak bunu naif bir yolla yaparsak ikinci dereceden bellek kullanımı elde ederiz: N kural zinciri varsa ve her kural bir dosya eklerse koleksiyon üyemiz 1+2+...+N olur.

Bu sorunu çözmek için NestedSet kavramını ortaya çıkardık. Diğer NestedSet örneklerinden ve kendi bazı üyelerinden oluşan bir veri yapısıdır. Böylece, kümelerin yönlendirilmiş döngüsel bir grafiği oluşturulur. 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, dosyalar ise Artifact sınıfının örnekleri olarak sunulur. 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 eserler. **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, bir kişinin bazı dosyaları bir tür arşiv halinde topladığı durumlarda kullanılır.
  3. Ağaç yapıları. Bunlar tek dosyalar değil, dizin ağaçlarıdır. İçindeki dosya grubu ve içerikleri kontrol edilerek güncel olup olmadığına bakılır. Bunlar TreeArtifact olarak gösterilir.
  4. Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler, yeniden derlemeyi tetiklemez. Bu, yalnızca derleme damgası bilgileri için kullanılır: 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ının temel bir nedeni yoktur. Sadece bu yapıyı henüz uygulamamışızdır (ancak BUILD dosyasında kaynak dizine referans vermek, Bazel'da bilinen ve uzun zamandır süregelen az sayıdaki hatadan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM mülkü tarafından etkinleştirilen bu tür bir uygulamamız vardır.)

Kayda değer bir Artifact türü aracıdır. Bunlar, MiddlemanAction çıktıları olan Artifact örnekle gösterilir. Bunlar, bazı şeylerin özel durumlar için kullanılır:

  • Toplayıcı aracılar, yapıları gruplandırmak için kullanılır. Böylece, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarlarımız değil, yalnızca N+M (iç içe yerleştirilmiş kümelerle değiştirilir) bağımlılık kenarlarımız vardır.
  • Bağımlılık aracılarını planlamak, bir işlemin diğerinden önce çalıştırılmasını sağlar. Bunlar, çoğunlukla hata analizi için kullanılır ama aynı zamanda C++ derlemesi için de kullanılır (açıklama için CcCompilationContext.createMiddleman() adresini ziyaret edin).
  • 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ç duyulan ortam ve ürettiği çıkış kümesi olarak en iyi şekilde anlaşılabilir. Bir işlem tanımının ana bileşenleri şunlardır:

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

İçeriği Bazel'in bildiği bir dosya yazmak gibi başka özel durumlar da vardır. Bunlar, AbstractAction dersinin 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 öğesine taşımak istiyoruz. JavaCompileAction oldukça yakındır ancak C++, .d dosyası ayrıştırması ve taramayı içermesi nedeniyle biraz özel bir kullanımdır.

Eylem grafiği, genellikle Skyframe grafiğine "yerleştirilmiştir": Kavramsal olarak bir işlemin yürütülmesi ActionExecutionFunction çağrısı olarak gösterilir. 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, Artifact.getGeneratingActionKey(), onu oluşturan işlemin anahtarını bulmak için kullanılır.
  • İç içe yerleştirilmiş 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ına ve paketlerine göre belirlenen bir dizine yerleştirmelerine izin verildiğinden daha sınırlıdır (ancak yine de aynı paketteki kurallar çakışabilir). Java'da uygulanan kurallar ise türetilmiş yapıları herhangi bir yere yerleştirebilir.

Bunun yanlış bir özellik olduğu kabul edilir, ancak bundan kurtulmak gerçekten zordur. Çünkü örneğin, bir kaynak dosyasının bir şekilde işlenmesi gerektiğinde ve bu dosyaya birden fazla kuralla (el dalgası el dalgası) referans verildiğinde yürütme süresinde önemli tasarruflar sağlar. Bu işlem için bir miktar RAM ödenmesi gerekir: Paylaşılan eylemin her bir ö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 denklik ilişkisi Actions.canBeShared() içinde uygulanır ve analiz ile yürütme aşamaları arasında her İşleme bakılarak 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 ile kodlanmıştır. Özetle, komut satırındaki yapılandırılmış hedeflerin filesToBuild değeri ve özel çıkış grubunun içeriği, "bu hedef komut satırındaysa bu yapıları oluştur" ifadesiyle açıkça belirtilir.

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 işlem SymlinkForest sınıfı tarafından gerçekleştirilir ve analiz aşamasında kullanılan her hedefi not ederek ve her paketi gerçek konumundaki kullanılan bir hedefle sembolik olarak bağlayan tek bir dizin ağacı oluşturarak çalışır. Alternatif bir yöntem de, komutlara doğru yolları iletmektir (--package_path dikkate alınarak). Bu istenmeyen bir durumdur çünkü:

  • Bir paket bir paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (eskiden yaygın olarak karşılaşılan 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ının değiştirilmesi, işlem önbelleği girişini geçersiz kılar
  • --package_path yavaş yavaş kullanımdan kaldırılıyor

Ardından Bazel, eylem grafiğinde (eylemlerden ve bunların giriş ve çıkış yapılarından oluşan iki partili, yönlendirilmiş grafik) gezinmeye ve işlemleri çalıştırmaya başlar. Her bir işlemin yürütülmesi SkyValue sınıfının ActionExecutionValue bir örneğiyle gösterilir.

Bir işlemi çalıştırmak pahalı olduğundan, Skyframe'in arkasından vurulabilecek birkaç önbellek 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 önbelleğini de içerir.

Yerel işlem önbelleği

Bu önbellek, Skyframe'ın arkasında bulunan başka bir katmandır. Bir işlem Skyframe'da 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 isabetlerinin alınabileceği anlamına gelir.

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

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

  1. Giriş ve çıkış dosyası grubu ve bunların sağlama toplamı
  2. "İşlem anahtarı", genellikle yürütülen komut satırı olmakla birlikte genel olarak giriş dosyalarının sağlama toplamı tarafından yakalanmayan her şeyi temsil eder (FileWriteAction için yazılan verilerin sağlama toplamı gibi).

Ayrıca, hâlâ geliştirme aşamasında olan son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" vardır. Bu önbellek, önbelleğe çok fazla gitmekten kaçınmak için geçişli karmalardan yararlanır.

Giriş keşfi ve giriş ayıklama

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 eylem, yürütmeden önce yeni girişler keşfedebilir veya bazı girişlerinin 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 yöntem, derleyiciye dayandığından ekleme tarayıcısından daha iyi bir tahmin sunar.

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

  1. Action.discoverInputs() çağrıldı. Gerekli olduğu belirlenen bir dizi iç içe yerleştirilmiş yapı döndürmelidir. İşlem grafiğinde, yapılandırılmış hedef grafikte eşdeğeri olmayan bağımlılık kenarları olmaması için bunlar kaynak yapıları olmalıdır.
  2. İşlem, Action.execute() çağrılarak yürütülür.
  3. Action.execute() programının sonunda işlem Action.updateInputs() öğesini çağırarak Bazel'a tüm girişlerine ihtiyaç duyulmadığını söyleyebilir. Kullanılan bir girişin kullanılmadığı bildirilirse bu durum yanlış artımlı derlemelere neden olabilir.

İşlem önbelleği yeni bir Action örneğinde (ör. sunucu yeniden başlatıldıktan sonra oluşturulan) isabet döndürdüğünde Bazel, giriş grubunun daha önce yapılan giriş keşfi ve budama işleminin sonucunu yansıtması için updateInputs() kendisini ç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.

İşlemleri çalıştırmanın çeşitli yolları: Stratejiler/İşlem Bağlamları

Bazı işlemler farklı şekillerde çalıştırılabilir. Örneğin, bir komut satırı yerel olarak, yerel olarak ancak çeşitli korumalı alanlarda veya uzaktan yürütülebilir. Bunu 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 örneğe 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. Uygun işlem bağlamı, mevcut olanlar arasından seçilir ve ActionExecutionContext ve BlazeExecutor özelliklerine yönlendirilir .
  3. İşlemler, ActionExecutionContext.getContext() ve BlazeExecutor.getStrategy() kullanarak bağlam ister (bunu yapmanın tek bir yolu vardır...)

Stratejiler, görevlerini yerine getirmek için başka stratejilerden yararlanabilir. Bu, örneğin eylemleri hem yerel hem de uzaktan başlatan dinamik stratejide, daha sonra hangisinin önce bittiğinin kullanıldığı dinamik stratejide kullanılır.

Kayda değer bir strateji, kalıcı çalışan süreçlerini uygulayan uygulamadır (WorkerSpawnStrategy). Bazı araçlar uzun başlatma süresine sahiptir ve bu nedenle her işlem için yeni bir tane başlatmak yerine işlemler arasında yeniden kullanılması gerekir (Bazel, çalışan sürecinin bağımsız istekler arasında gözlemlenebilir durum taşımayacağına dair söz verdiği için bu potansiyel bir doğruluk sorunudur).

Araç değişirse çalışan 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. Hangi eylem girişlerinin aracın bir parçasını ve hangilerinin girişleri temsil ettiğini bilmek önemlidir. Bu, İşlemi oluşturan kişi (Spawn.getToolFiles()) tarafından belirlenir ve Spawn ç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ırmaya yönelik çeşitli stratejilerle ilgili bilgileri burada bulabilirsiniz.
  • Hangisinin önce tamamlandığını görmek için hem yerel hem de uzaktan işlem gerçekleştirdiğimiz dinamik stratejiyle ilgili bilgiler burada bulunur.
  • Yerel olarak işlem yürütmenin zorlukları hakkında buradan bilgi edinebilirsiniz.

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ı açıklamasını burada bulabilirsiniz.

Çıkış dizininin yapısı

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

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

Belirli bir yapılandırmayla ilişkilendirilen 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 "yaklaşık olarak" aynı şeyi temsil ediyorsa aynı ada sahip olmalıdır. Böylece, komut satırları eşleşirse birinde yürütülen işlemler diğerinde yeniden kullanılabilir: Örneğin, Java derleyicisinin komut satırı seçeneklerinde yapılan değişiklikler C++ derleme işlemlerinin yeniden çalıştırılmasına neden olmamalıdır.

Şu ana kadar, bu sorunu çözmek için yapılandırma kırpma sorunuyla benzerlikleri olan ilkeli bir yöntem bulamadık. Seçenekler hakkında daha uzun bir açıklamayı burada bulabilirsiniz. Temel sorun alanları, Starlark kuralları (yazarlarının genellikle Bazel'i yakından tanımadığı) ve "aynı" çıkış dosyasını oluşturabilecek öğelerin alanına başka bir boyut katan yönlerdir.

Mevcut yaklaşım, Java'da uygulanan yapılandırma geçişlerinin işlem çakışmasına neden olmaması için yapılandırma yolu segmentinin <CPU>-<compilation mode> olması ve çeşitli son eklerin eklenmesidir. Ayrıca, kullanıcıların işlem çakışmasına neden olmaması için Starlark yapılandırma geçişi grubunun sağlama toplamı eklenir. Bu durum hiç de mükemmel değil. 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 (uzaktan yürütme arka ucu varsa)
  • Testleri paralel olarak birden çok kez çalıştırmak (zamanlama verilerini ayıklamak veya 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 paketlerine gruplandırma

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

  • Derlemesi testin çalıştırılmasına neden olan yapılardır. Bu, serileştirilmiş bir TestResultData mesajı içeren bir "önbelleğe alma 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 gerçekleştirileceğini 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. Bir test paketinin test bildirmediğini belirtmesi, paketindeki her testi kapsaması biraz şaşırtıcı bir kırışıklıktır. 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 veriler daha sonra BuildView.createResult()'te daha ayrıntılı şekilde işlenir: Analizi başarısız olan hedefler filtrelenir ve testler özel ve özel olmayan testlere ayrılır. Ardından, AnalysisResult içine yerleştirilir. ExecutionTool bu şekilde hangi testleri çalıştıracağı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ıldığını belirtmek üzere tests()sorgu operatörü (TestsFunction'te uygulanır) kullanılabilir. Ne yazık ki yeniden uygulandığı için muhtemelen yukarıdakilerden birçok açıdan farklıdır.

Test çalıştırma

Testlerin çalıştırılma şekli, önbellek durumu yapıları istemektir. 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 testlere bildirmek 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 kırığındaki her bir test durumunu ayrıntılı olarak gösteren JUnit stili bir XML dosyası
  • test.log, testin konsol çıkışıdır. stdout ve stderr ayrılmaz.
  • test.outputs, "bildirilmemiş çıkışlar dizini"dir. Terminale yazdırdıklarına ek olarak dosya çıkışını 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 özel modda (örneğin, diğer testlere paralel olarak değil) yürütülmesi gerekir. Bu, test kuralına tags=["exclusive"] eklenerek 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 yürütü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üreli testin ilerlemesi hakkında bilgi almak için testlerin sonucunun akışa aktarılmasını isteyebilir. Bu, --test_output=streamed komut satırı seçeneğiyle belirtilir ve farklı testlerin çıkışlarının karışmaması için özel test yürütülmesi 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, dosyalardaki LCOV biçiminde testler tarafından raporlanır bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

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 çok alt işlem yürütebilir ve birden fazla farklı programlama dilinde (ayrı kapsam toplama çalışma zamanlarıyla) yazılmış bölümlerden 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, --coverage_support yapılandırma işaretinin değerine çözümlenen örtülü :coverage_support özelliği tarafından sağlanır (bkz. TestConfiguration.TestOptions.coverageSupport).

Bazı diller çevrimdışı enstrümantasyonu yapar, yani kapsam araçları derleme sırasında (ör. C++) eklenirken bazıları da online enstrümantasyon gerçekleştirir. Yani, kapsam araçları yürütme sırasında eklenir.

Referans kapsamı da temel kavramlardan biridir. Bu, bir kitaplık, ikili program veya testte hiçbir kod çalıştırılıp çalıştırılmadığını kapsar. 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 hedef için referans kapsam dosyası bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat şeklindedir . Ayrıca, --nobuild_tests_only işaretini Bazel'e iletirseniz testlere ek olarak ikili programlar 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ümantasyonlu dosyalar grubu da adından da anlaşılacağı üzere bir dizi dosyadan ibarettir. Ç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 özel dosyalardan oluşur; örneğin, derleme sırasında gcc .gcno dosyalarını yayar. Kapsam modu etkinse bunlar test işlemlerinin giriş grubuna eklenir.

Kapsamın toplanıp toplanmadığı BuildConfiguration içinde saklanır. Bu yöntem, test işlemini ve eylem grafiğini bu bite bağlı olarak değiştirmenin kolay bir yolu olduğundan faydalıdır. Ancak bu bit değiştirilirse tüm hedeflerin yeniden analiz edilmesi gerekir (C++ gibi bazı diller, kapsamı toplayabilecek kod yaymak için farklı derleyici seçenekleri gerektirir ve bu durumda da yeniden analiz yapılması gerekir. Bu da yine de yeniden analiz yapılması gerekir).

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, bir Bazel çağrısında her test için toplanan kapsamı birleştiren bir "kapsam raporu" oluştururuz. Bu, CoverageReportActionFactory tarafından işlenir ve BuildView.createResult() kaynağından ç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 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ğini incelemek için kullanılır
  • bazel aquery, işlem grafiğini incelemek için kullanılır

Bunların her biri, AbstractBlazeQueryEnvironment sınıfının alt sınıfı olarak uygulanır. QueryFunction alt sınıflandırılmasıyla ek sorgu işlevleri yapılabilir. Akış sorgusu sonuçlarına izin vermek için bunları bir veri yapısına toplamak yerine QueryFunction öğesine bir query2.engine.Callback iletilir. Bu da, döndürmek istediği sonuçlar için bu sonuçları çağırır.

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) hassas bir koşulu, Bazel'in paket yüklemenin sağladığı _tüm_ bilgileri yayınlamasıdır. Böylece, çıkışı karşılaştırabilir ve belirli bir hedefin değişip değişmediğini belirleyebilirsiniz. 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. Normal çözüm, bir etiket kullanmak ve karmaşık bilgileri bu etikete sahip kurala eklemektir. 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. Bunu iyi 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 etkinliklerden bazıları, Etkinlik Oluşturma Protokolü'nde Bazel dışında temsil edilir (BuildEvent olur). Bu, yalnızca BlazeModule'lere değil, aynı zamanda Bazel işlemi dışındaki öğelerin de derlemeyi gözlemlemesini sağlar. Bunlara, protokol mesajlarını içeren bir dosya olarak erişilebilir ya da Bazel etkinlik akışı sağlamak için bir sunucuya (Derleme Etkinliği Hizmeti olarak adlandırılır) bağlanabilir.

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

Harici depolar

Bazel başlangıçta monorepo'da (bir uygulamayı derlemek için gereken her şeyi içeren tek bir kaynak ağacı) kullanılmak üzere tasarlanmış olsa da Bazel'in bu durumun geçerli olmadığı bir dünyada yaşadığını söyleyebiliriz. "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ı depodaki 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 endeksinin X'inci load() ifadesine kadar değerlendirilmesi anlamına gelene kadar hesaplamak WorkspaceFileFunction ile gösterilir.

Depoları getirme

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

Kod deposunu getirme işlemi aşağıdaki adımlarla 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ı üzerinde yineleme yaparak getirmesi istenen depo kuralını bulur
  4. Depo getirme işlemini uygulayan uygun RepositoryFunction bulundu. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için kodlu bir haritadır.

Bir depo getirmek ç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 özellik, çalıştıkları çalışma alanı 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ındaki her depo için bir "işaretçi dosyası" yazılır. Bu dosya, dosyayı getirmek için kullanılan kuralın sağlama toplamını içerir. 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. Bu durum bir sorun teşkil eder. Çünkü Bazel, genellikle kaynak yapılarda stat() çağrısı yaparak güncellik olup olmadığını kontrol eder. Ayrıca, bu yapılar ayrıca depolarının tanımı değiştiğinde geçersiz hale gelir. Bu nedenle, harici depodaki bir yapı için FileStateValue'lerin harici depolarına bağlı olması gerekir. Bu işlem ExternalFilesHelper tarafından yönetilir.

Yönetilen dizinler

Bazen harici depoların, çalışma alanı kökü altındaki dosyaları (indirilen paketleri kaynak ağacının bir alt dizininde barındıran paket yöneticisi gibi) 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 çalışma alanının 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 deposun aynı depoya bağlı olmak istemesi ancak farklı sürümlerde olması (bu, "elmas bağımlılık sorunu"nun bir örneğidir) mümkündür. Örneğin, derlemedeki ayrı depolardaki iki ikili dosya Guava'ya bağımlı olmak istiyorsa muhtemelen her ikisi de Guava'yı @guava// ile başlayan etiketlerle ifade eder ve bunun Guava'nın farklı sürümleri anlamına gelmesini bekler.

Bu nedenle Bazel, harici depo etiketlerini yeniden eşlemenize olanak tanır. Böylece @guava// dizesi, bir ikili dosyanın deposundaki bir Guava deposunu (@guava1// gibi) ve diğer ikili dosyanın deposundaki başka bir Guava deposunu (@guava2// gibi) referans alabilir.

Alternatif olarak bu, 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 bağımsız depo tanımlarının 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 genellikle Java dilinde 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şlemi (bazen uzaktan) çalıştırmanın birleşmesi, ayrıntılı önbelleğe alma, güzel ve renkli bir terminal çıkışına sahip olma isteği ve uzun süre çalışan bir sunucuya sahip olma isteği onu önemsiz hale getirir.

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

Çıkış varsayılan olarak ANSI kod dışı bırakma sıralarıyla yazdırılır. Bunlar istenmediğinde (--color=no), AnsiStrippingOutputStream ile çıkarılır. Ayrıca System.out ve System.err bu çıkış akışlarına yönlendirilir. Bunun nedeni, hata ayıklama bilgilerinin System.err.println() kullanılarak basılabilmesi ve yine de istemcinin terminal çıkışına (sunucununkinden farklı) gönderilebilmesidir. 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. Bunlar, EventBus'te yayınlanan içeriklerden farklıdır (bu durum kafa karıştırıcıdır). Her Event bir EventKind içerir (hata, uyarı, bilgi ve birkaç tane daha) ve bir Location (kaynak kodda etkinliğin gerçekleştiği yer) olabilir.

Bazı EventHandler uygulamaları, aldıkları etkinlikleri depolar. Bu özellik, önbelleğe alınmış çeşitli işlemlerin (ör. önbelleğe alınmış yapılandırılmış bir hedefin yayınladığı uyarılar) neden olduğu çeşitli önbellek işlemlerinin neden olduğu bilgileri kullanıcı arayüzünde tekrar 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 uygular, ancak EventBus ürününe yayınlanan her şey bu arayüzü uygulamaz; yalnızca bir ExtendedEventHandler tarafından önbelleğe alınanlar (bu güzel olur ve işlemlerin çoğu yapar; yine de zorunlu kılınmaz)

Terminal çıkışı çoğunlukla UiEventHandler aracılığıyla yayınlanır. Bu, Bazel'in yaptığı tüm süslü çıkış biçimlendirmesi ve ilerleme raporlaması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 Profil Oluşturma

Bazel hızlıdır. Bazel de yavaştır çünkü derlemeler mümkün olanın sınırına kadar büyüme eğilimindedir. 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ı şeklindedir: Görevler bir şekilde başlatılıp bitirilebilir ve bunların birbirlerine düzgün bir şekilde yerleştirilmiş olması 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. Profile öğe eklemek için Profiler.instance().profile() numaralı telefonu arayın. Kapanış değeri görevin sonunu temsil eden bir Closeable döndürür. En iyi performansı try-with-resources ifadeleriyle gösterir.

MemoryProfiler ürününde de temel bellek profili oluşturma işlemi gerçekleştiriyoruz. 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 tür test vardır: Bazel'i "kara kutu" olarak gözlemleyen testler ve yalnızca analiz aşamasında yürütülen 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 testi ç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 alt sınıfları olarak uygulanır. BUILD dosyalarını yazmak için kullanabileceğiniz bir tarama dosya sistemi bulunur. Ardından, çeşitli yardımcı yöntemler yapılandırılmış hedefler isteyebilir, yapılandırmayı değiştirebilir ve analizin sonucu hakkında çeşitli bilgiler sunabilir.