Bu belge, kod tabanının ve Bazel'in yapısının açıklamasıdı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'ın kod tabanı büyüktür (~350 KLOC üretim kodu ve ~260 KLOC test kodu). Hiç kimse tüm kod tabanına hakim değildir. Herkes kendi alanını çok iyi bilir ancak her yöndeki tepelerin arkasında ne olduğunu bilen çok az kişi vardır.
Bu belge, yolculuğun ortasında olanların kendilerini düz yol kaybolmuş karanlık bir ormanda bulmaması için kod tabanına genel bir bakış sunarak üzerinde çalışmaya başlamayı kolaylaştırmayı amaçlar.
Bazel'in kaynak kodunun herkese açık sürümü, GitHub'da github.com/bazelbuild/bazel adresinde bulunur. Bu, "gerçeğin 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ını sağlar.
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 sonra (-c opt) gelir. İlk tür "başlatma seçeneği" olarak adlandırılır ve sunucu sürecini bir bütün olarak etkiler. İkinci tür olan "komut seçeneği" ise 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. Bu durum, özel bir çıkış tabanı belirtilerek ö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:
- Kendini daha önce çıkarıp çıkarmadığını kontrol eder. Değilse bunu yapar. Sunucunun uygulanması bu noktada başlar.
- Ç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. Bu komut, sunucunun dinlediği bağlantı noktasını içeren bir kilit dosyasının bulunduğu
$OUTPUT_BASE/serverdizine bakarak çalışan sunucuyu bulur. - Gerekirse eski sunucu sürecini sonlandırır.
- Gerekirse yeni bir sunucu işlemi 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ı olan 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
ve BlazeRuntime içindeki bazı durumların yaşam döngüsüdür.
Bazel sunucusu, bir komutun sonunda 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 müşteriye hangi ikiliyi exec() ve hangi argümanlarla 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çıklama için Çıkış dizini düzeni bölümüne bakın.
"Ana depo", Bazel'in çalıştırıldığı kaynak ağaçtır. Genellikle kaynak kontrolünden kontrol ettiğiniz 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 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> konumunda 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 şunları içerir:
$OUTPUT_BASE/externalkonumunda 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/execrootadresinde 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$EXECROOTolarak 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:
BlazeCommandDispatcheryeni 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.Doğru komut bulunur. Her komut,
BlazeCommandarayüzünü uygulamalı ve@Commandek açıklamasına sahip olmalıdır (Bu biraz antipattern'dir. Bir komutun ihtiyaç duyduğu tüm meta verilerinBlazeCommandüzerindeki yöntemlerle açıklanması iyi olurdu).Komut satırı seçenekleri ayrıştırılır. Her komutun farklı komut satırı seçenekleri vardır. Bu seçenekler
@Commandek açıklamasında açıklanmıştır.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.
Komut kontrolü ele geçirir. En ilginç komutlar, derleme çalıştıran komutlardır: build, test, run, coverage vb. Bu işlev,
BuildTooltarafından uygulanır.Komut satırındaki hedef kalıplar grubu ayrıştırılır ve
//pkg:allile//pkg/...gibi joker karakterler çözümlenir. Bu,AnalysisPhaseRunner.evaluateTargetPatterns()içinde uygulanır ve Skyframe'deTargetPatternPhaseValueolarak somutlaştırılır.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.
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 harita içerir. "Seçenek sınıfı", OptionsBase alt sınıfıdır ve birbiriyle ilişkili komut satırı seçeneklerini birlikte gruplandırır. Örneğin:
- Bir programlama diliyle (
CppOptionsveyaJavaOptions) ilgili seçenekler. BunlarFragmentOptions'nin bir alt sınıfı olmalı ve sonunda birBuildOptionsnesnesine sarmalanmalıdır. - 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 durum böyle değildir ve bunları değiştirmek, Bazel'i hata ayıklaması zor olan ince yöntemlerle bozmanın gerçekten iyi bir yoludur. Ancak bunları gerçekten değiştirilemez hale getirmek büyük bir çaba gerektirir.
(Bir FragmentOptions öğesini oluşturulduktan hemen sonra, başka biri ona referans tutma şansı bulmadan ve equals() veya hashCode() öğesi üzerinde çağrılmadan önce değiştirmek sorun değildir.)
Bazel, seçenek sınıfları hakkında aşağıdaki şekillerde bilgi edinir:
- Bazıları Bazel'e doğrudan kodlanmıştır (
CommonCommandOptions). - Her Bazel komutundaki
@Commandek açıklamasından ConfiguredRuleClassProvider(Bunlar, tek tek programlama dilleriyle ilgili komut satırı seçenekleridir.)- 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ürlerin seçeneklerini 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 bir uygulaması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 kod; 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 depoya yayılan 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ış konusuna bakın.
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, her harici depoyu da $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. Her ikisi 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 bu nedenle 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. Bu, çoğunlukla bir dizenin (hedefin adı) hedefle eşleştirildiği 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 yapılması gerekir. İ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:
- Dosyalar: Derlemenin girişi veya çıkışı olan öğeler. Bazel dilinde bunlara yapılar (artifacts) denir (başka bir bölümde 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.
- Kurallar: Çıkışları girişlerden elde etme adımlarını açıklar. Genellikle bir programlama diliyle (ör.
cc_library,java_libraryveyapy_library) ilişkilendirilirler ancak dilden bağımsız olanlar da vardır (ör.genruleveyafilegroup). - 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, name ise dosyanın (etiket bir kaynak dosyayı ifade ediyorsa) paket dizinine göre yolu anlamına gelir. Komut satırında bir hedeften bahsederken etiketin bazı kısımları atlanabilir:
- Depo atlanırsa etiketin ana depoda olduğu varsayılır.
- Paket kısmı atlanırsa (ör.
nameveya:name), etiketin mevcut ç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:
- Özellikleri (ör.
srcs,deps): türleri, varsayılan değerleri, kısıtlamaları vb. - Her bir özellikle ilişkili yapılandırma geçişleri ve yönleri (varsa)
- Kuralın uygulanması
- 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, kenarları herhangi bir veri parçasından bağımlılıklarına (yani oluşturulması için bilinmesi gereken diğer veri parçaları) doğru yönlendirilmiş bir yönlü döngüsüz grafik halinde düzenlenmesidir.
Grafikteki düğümlere SkyValue, adlarına ise SkyKey adı verilir. Her ikisi de değişmezdir. Yalnızca değişmez nesnelere erişilebilir. Bu değişmez kural neredeyse her zaman geçerlidir. Geçerli olmadığı durumlarda (ör. BuildOptions sınıfı, BuildConfigurationValue sınıfının üyesi ve SkyKey sınıfı için) bu sınıfları değiştirmemeye veya yalnızca dışarıdan gözlemlenemeyecek şekilde değiştirmeye çalışırız.
Bu nedenle, Skyframe içinde 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 iyi sonucu küçük derlemelerde alırsınız.
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 sonra kullanılamayan bağımlılığı değerlendirir ve işlevi baştan başlatır. Bu sefer getValue() çağrısı boş olmayan bir sonuçla başarılı olur.
Bu durum, yeniden başlatmadan önce SkyFunction içinde yapılan tüm hesaplamaların tekrarlanması gerektiği anlamına gelir. 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:
- Yeniden başlatma sayısını sınırlamak için bağımlılıkları gruplar halinde (
getValuesAndExceptions()kullanarak) bildirme. - Farklı
SkyFunction'ler tarafından hesaplanan birSkyValue'yı ayrı parçalara bölerek bağımsız olarak hesaplanıp önbelleğe alınabilmelerini sağlar. Bu işlem, bellek kullanımını artırabileceğinden stratejik olarak yapılmalıdır. - Yeniden başlatmalar arasında durumu saklama (
SkyFunction.Environment.getState()kullanılarak veya "Skyframe'in arkasında" geçici bir statik önbellek tutularak). Karmaşık SkyFunctions ile yeniden başlatmalar arasındaki durum yönetimi zorlaşabilir. Bu nedenle,StateMachines,SkyFunctioniçinde hiyerarşik hesaplamaları askıya alma ve devam ettirme kancaları da dahil olmak üzere mantıksal eşzamanlılığa yönelik yapılandırılmış bir yaklaşım için kullanıma sunuldu. Örnek:DependencyResolver#computeDependenciesStateMachineilegetState()kullanarak, yapılandırılmış bir hedefin potansiyel olarak çok büyük olan doğrudan bağımlılıklar kümesini hesaplar. Bu küme, aksi takdirde 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:
BUILDdosyası. Yeni derleme hedefleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu yalnızcaBUILDdosyasının içeriğine ve bu dosya tarafından yüklenen.bzldosyalarına erişebilir.MODULE.bazeldosyası. Dış bağımlılıklar burada tanımlanır. Bu bağlamda çalışan Starlark kodu, yalnızca önceden tanımlanmış birkaç yönergeye çok sınırlı erişime sahiptir..bzldosyası. 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.bzldosyaları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:
- Paketleri yükleme (diğer bir deyişle
BUILDdosyalarını, bunları temsil edenPackagenesnelerine dönüştürme) - 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ışı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:
- 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)
- Doğrudan bağımlılıklar. Geçişli bilgi sağlayıcıları, analiz edilen kural için kullanılabilir. Bu türler, yapılandırılmış hedefin geçişli kapanışı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ğladıkları için bu şekilde adlandırılır.
- Hedefin kendisi. Bu, hedefin bulunduğu paketin yüklenmesinin sonucudur. Kurallar için bu, genellikle önemli olan özelliklerini içerir.
- 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 şöyledir:
- Kendisine bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar erişebilir.
- 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 daha kolaydır. Örneğin, zaman veya alan karmaşıklığı ikinci dereceden (ya da daha kötü) olan kod yazmak, Bazel sunucusunun Java istisnasıyla kilitlenmesine neden olmak veya değişmezleri ihlal etmek (örneğin, bir Options örneğini yanlışlıkla değiştirerek veya yapılandırılmış bir hedefi değiştirilebilir hale getirerek)
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. Bu, örneğin, derleme sırasında çalıştırılan bir araç ve hedef kod için aynı kod kullanıldığında ve çapraz derleme yaptığımızda veya çoklu CPU mimarileri için yerel kod içeren bir fat Android uygulaması oluşturduğumuzda kullanışlıdır.
Kavramsal olarak yapılandırma bir BuildOptions örneğidir. Ancak uygulamada BuildOptions, çeşitli ek işlevler sağlayan BuildConfiguration ile sarmalanır. Bağımlılık grafiğinin üst kısmından alt kısmına doğru yayılır. Değişirse derlemenin yeniden analiz edilmesi gerekir.
Bu durum, ö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 belirtmesi gerekir. Bu, hem hataları (ör. Java parçasını kullanan Python kuralları) önlemek 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:
- Bağımlılık kenarında. Bu geçişler
Attribute.Builder.cfg()içinde belirtilir veRule(geçişin gerçekleştiği yer) ileBuildOptions(orijinal yapılandırma) öğesinden bir veya daha fazlaBuildOptions(çıkış yapılandırması) öğesine geçiş işlevleridir. - 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:
- 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
- Belirli bir bağımlılığın birden fazla mimari için (ör. kalın 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_ yolunun) yanı sıra kendilerine bağlı olan diğer yapılandırılmış hedeflere kendileri hakkında bilgi vermenin de tek yoludur. Bu metriklerin adında "geçişli" kelimesinin 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 olanlar 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:
- Java Class nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar için kullanılabilir. Bu sağlayıcılar,
TransitiveInfoProvideralt sınıfına girer. - Bir dize. Bu yöntem eski bir yöntemdir ve ad çakışmalarına karşı hassas olduğundan 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 . - 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'daProvider.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 fırsat 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:
filesToBuild, "bu kuralın temsil ettiği dosyalar kümesi" gibi belirsiz bir kavramdır. Bunlar, yapılandırılan hedef komut satırında veya bir genrule'un srcs'inde olduğunda oluşturulan dosyalardır.- Çalıştırma dosyaları, normal ve veri.
- Çı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
OutputGroupInfosağlayıcısı kullanılarak erişilebilir.
Runfiles
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 durum, iki nedenden dolayı tek bir Map simgesinden biraz daha karmaşıktır:
- Çoğu zaman, bir dosyanın runfiles yolu, execpath'iyle aynıdır. Bu, RAM'den tasarruf etmemizi sağlar.
- Çalıştırma dosyası ağaçlarında, gösterilmesi gereken çeşitli eski giriş türleri 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, arka planda 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ğımlı 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ş çalıştırılabilir dosyaları manifesti. Bu, runfiles ağacının seri hâlinde açıklanmış halidir. Bu, runfiles ağacının içeriği için proxy olarak kullanılır ve Bazel, yalnızca manifestin içeriği değiştiğinde 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.
RunfilesSupportnesnesinin 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 burada açıklanmıştır. İyi bir motivasyon örneği, protokol arabellekleridir: Bir proto_library kuralı, belirli bir dil hakkında bilgi sahibi olmamalıdır. Ancak bir protokol arabellek mesajının (protokol arabelleklerin "temel birimi") herhangi bir programlama dilinde uygulanması, aynı dildeki iki hedef aynı protokol arabelleğe bağlıysa yalnızca bir kez oluşturulması için proto_library kuralıyla birlikte kullanılmalı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 kümesi, 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:
AspectClass, bu özelliğin uygulanmasıdır. Java'da (bu durumda alt sınıftır) veya Starlark'ta (bu durumdaStarlarkAspectClassörneğidir) olabilir.RuleConfiguredTargetFactoryile benzerdir.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 uygunAspectClassörneği gibi, uygulamasının referansını içerir.RuleClassile benzerdir.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.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.RuleAspect, belirli bir kuralın hangi yönlerinin yayılması gerektiğini belirleyen işlevdir.Rule->Aspectiş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 karmaşıklığı sınıfta AspectCollection gösterilir.
Platformlar ve araç zincirleri
Bazel, çok platformlu derlemeleri (yani derleme işlemlerinin çalıştırıldığı birden fazla mimarinin ve kodun oluşturulduğu birden fazla mimarinin olabileceği derlemeler) destekler. 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, ayarlanmış yürütme ve hedef platforma göre kullanılan C++ derleyicisini 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ı kümesiyle açıklama eklenir. Bunu yapmak için araç zincirinin tanımı iki bölüme ayrılır:
- Bir araç zincirinin desteklediği yürütme ve hedef kısıtlamaları kümesini açıklayan ve ne tür (ör. C++ veya Java) bir araç zinciri olduğunu belirten bir
toolchain()kuralı (ikincisitoolchain_type()kuralıyla temsil edilir) - 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üklenmeleri daha uzun sürer.
Yürütme platformları aşağıdaki yöntemlerden biriyle belirtilir:
register_execution_platforms()işlevini kullanarak MODULE.bazel dosyasında- --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, aşağıdakilerin bir işlevidir:
- 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ılmış hedefin (
exec_compatible_withözelliği) yürütme platformu kısıtlamaları kümesi,UnloadedToolchainContextKeycinsinden
Sonuç olarak, araç zinciri türünden (ToolchainTypeInfo örneği olarak gösterilir) seçilen araç zincirinin etiketine kadar olan bir eşleme olan UnloadedToolchainContext elde edilir. 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 vardır . 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 Java kuralları için Google'da 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:
restricted_to=özelliği aracılığıyla. Bu, en doğrudan belirtme biçimidir. Kuralın desteklediği ortamların tam kümesini bildirir.compatible_with=özelliği aracılığıyla. Bu, bir kuralın desteklediği ortamları, varsayılan olarak desteklenen "standart" ortamlara ek olarak bildirir.- Paket düzeyindeki
default_restricted_to=vedefault_compatible_with=özellikleri aracılığıyla. 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.- 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
*_testkuralların test edilebilir hale getirilmesi için kullanılabilir.
environment() normal bir kural olarak uygulanırken environment_group() hem Target öğesinin bir alt sınıfıdır ancak Rule (EnvironmentGroup) değildir hem de Starlark'ta (StarlarkLibrary.environmentGroup()) varsayılan olarak kullanılabilen ve 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'te 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 kodunuza rastgele bağımlı olmasını önlemek için dikkatli olmanız gerekir. Aksi takdirde, Hyrum'un yasasına göre, kullanıcılar uygulama ayrıntıları olarak değerlendirdiğiniz 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 hedefi işaret etmek yerine paket adları üzerinde bir kalıbı kodlayabilir. (Evet, bu bir tasarım hatasıdır.)
Bu özellik aşağıdaki yerlerde uygulanır:
RuleVisibilityarayü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/...kullanan 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:PackageSpecification(//pkg/...gibi tek bir kalıba karşılık gelir),PackageGroupContents(tek birpackage_groupöğesininpackagesözelliğine karşılık gelir) vePackageSpecificationProvider(birpackage_groupöğesi ile geçişliincludesöğesi üzerinde toplama yapar). - Görünürlük etiketi listelerinden bağımlılıklara dönüştürme işlemi
DependencyResolver.visitTargetVisibilityve 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ı
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ştirilemez 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ı üretmek için çalıştırılması gereken bir dizi komuttan oluşur. Komutlar Action sınıfının örnekleri, dosyalar ise Artifact sınıfının örnekleri olarak gösterilir. Bu 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:
- Normal yapılar. Bunların güncelliği, sağlama toplamları hesaplanarak kontrol edilir. mtime, kısayol olarak kullanılır. ctime'ı değişmemişse dosyanın sağlama toplamı hesaplanmaz.
- Çö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.
- 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. Bunlar
TreeArtifactolarak gösterilir. - 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ıların ağaç yapıları veya çözümlenmemiş sembolik bağlantı yapıları olmamasının temel bir nedeni yoktur. Bu özellik henüz uygulanmamıştır. Şu anda kaynak sembolik bağlantıları her zaman çözülür ve kaynak dizinler desteklenirken içerikleri derleme kuralları için tamamen opak olduğundan ağaç yapıları gibi aynı türde tembel komut satırı genişletmesini desteklemez.
İş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ı
- 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şturacak işlemi bulmak için kullanılır. - İç içe geçmiş kümelerin kendi Skyframe anahtarı vardır.
Paylaşılan işlemler
Bazı işlemler birden fazla yapılandırılmış hedef tarafından oluşturulur. Starlark kuralları, 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 tasarruflar 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.
İki işlem aynı çıkış dosyasını oluşturuyorsa bu işlemlerin tam olarak aynı olması gerekir:
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 derlemenin "global" görünümünü gerektiren Bazel'deki 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ılan 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 (--package_path) okuma seçeneğine sahip olduğundan, yerel olarak yürütülen işlemlere tam 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 gerçek konumundan kullanılan bir hedefle 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 yaygın bir durumdu).
- Bir işlem uzaktan çalıştırıldığında yerel olarak çalıştırıldığından farklı komut satırları oluşturulur.
- Kullanımdaki 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_pathyavaş yavaş ve kademeli olarak 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ıActionExecutionFunctionucuz hale getirecek veriler içeriyor- Yerel işlem önbelleği, dosya sisteminin durumuyla ilgili verileri içerir.
- Uzaktan yürütme sistemleri genellikle kendi önbelleklerini de içerir.
Yerel işlem önbelleği
Bu önbellek, Skyframe'in arkasında 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â isabet olabilir. Yerel dosya sisteminin durumunu gösterir 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:
- Giriş ve çıkış dosyaları kümesi ve bunların sağlama toplamı
- 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.
FileWriteActioniçin yazılan verilerin sağlama toplamıdır).
Ayrıca, önbelleğe çok fazla kez 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şten oluşmaz. 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. Kanonik örnek 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
#includeifadelerinde 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ılı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:
Action.discoverInputs()aranı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.- İşlem,
Action.execute()çağrılarak yürütülür. Action.execute()sonunda işlem, Bazel'e tüm girişlerinin gerekmediğini bildirmek içinAction.updateInputs()'i ç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 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:
- Yürütme aşaması başlatıldığında
BlazeModuleörneklerine hangi işlem bağlamlarına sahip oldukları sorulur. Bu durum,ExecutionToololuşturucusunda gerçekleşir. İşlem bağlamı türleri,Classalt arayüzüne başvuran bir JavaActionContextörneği ve işlem bağlamının hangi arayüzü uygulaması gerektiği ile tanımlanır. - Uygun işlem bağlamı, mevcut olanlar arasından seçilir ve
ActionExecutionContextileBlazeExecutoradresine yönlendirilir . - İşlemler,
ActionExecutionContext.getContext()veBlazeExecutor.getStrategy()kullanarak bağlam isteğinde bulunur (Aslında bunu yapmanın 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). Buradaki fikir, 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ğidir (Bu, Bazel'in çalışan sürecinin tek tek 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 parçasını, hangilerinin 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 hem de uzaktan işlem yaparak 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ıyla ilgili bilgilere 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çıklanmalıdır. 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ılabilir hale gelene kadar engellenir.
Yerel kaynak yönetimiyle ilgili daha ayrıntılı açıklamayı burada bulabilirsiniz.
Çıkış dizininin yapısı
Her işlem, çı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:
- 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ı çıkış 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ı").
- İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa aynı ada sahip olmalıdır. Böylece, komut satırları eşleşiyorsa 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.
Şu ana kadar bu sorunu ilkelere dayalı bir şekilde çözmenin bir yolunu bulamadık. Bu sorun, yapılandırma kırpma sorununa benzer. Seçenekler hakkında daha ayrıntılı bir açıklamayı burada bulabilirsiniz. Sorunlu olan başlıca alanlar, genellikle Bazel'e yakından 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 özellik mükemmel olmaktan uzaktır. 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 zengin destek sunar. Şunları destekler:
- Testleri uzaktan çalıştırma (uzaktan yürütme arka ucu varsa)
- Testleri paralel olarak birden çok kez çalıştırma (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
TestResultDatamesajı içeren "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 devam eder: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve özel olmayan testler olarak ayrılır. Ardından AnalysisResult'ya 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 özellik maalesef yeniden uygulandığı için yukarıdaki bilgilerden birçok küçük noktada farklılık gösterebilir.
Testleri çalıştırma
Testler, önbellek durumu yapıtları istenerek çalıştırılır. Bu işlem, TestRunnerAction yürütülmesine neden olur. Bu da sonunda testi istenen şekilde çalıştıran --test_strategy komut satırı seçeneği tarafından seçilen TestActionContext'i çağırır.
Testler, ortam değişkenlerini kullanarak testlerden ne beklendiğini belirten 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ına buradan ulaşabilirsiniz. En basit haliyle, 0 çıkış kodu başarı, diğer tüm çıkış kodları ise başarısızlık anlamına gelir.
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"ye 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ış 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 tetiklenebilir . 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 yoklayıp Bazel kurallarının geçerli olduğu terminale yeni baytlar dökerek çalışır.
Çalıştırılan 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 (ayrı kapsam toplama çalışma zamanlarıyla) oluşabilir. 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 araya yerleştirme işlemi test stratejileri tarafından yapılır ve test girişlerinde collect_coverage.sh bulunmasını gerektirir. 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. Bazı 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 da 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, kapsanan satır içermeyen ve yalnızca kapsama alanı verilerini topladığımız dosyaları içeren her ikili için bir kapsama alanı 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, yalnızca kaynak dosyaların adlarından daha anlamlı içeriklere sahip kendi temel kapsam dosyalarını oluşturması önerilir.
Her kural için kapsam toplama işleminde iki dosya grubu izlenir: 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. Ayrıca 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. Kapsam modu etkinse bunlar test işlemlerinin girişler kümesine eklenir.
Kapsamın toplanıp toplanmadığı bilgisi BuildConfiguration içinde saklanır. Bu, testi ve işlem grafiğini bu bite göre değiştirmenin kolay bir yolu olduğundan kullanışlıdır. 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ı, örtülü bağımlılıkta etiketler aracılığıyla bağımlı hale getirilir. Böylece, çağırma politikası tarafından geçersiz kılınabilir ve 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" da oluştururuz. Bu işlem CoverageReportActionFactory tarafından gerçekleştirilir ve BuildView.createResult() üzerinden çağrılır . Bu işlev, çalıştırılan ilk testin :coverage_report_generator özelliğine bakarak ihtiyaç duyduğu araçlara erişir.
Sorgu motoru
Bazel, çeşitli grafiklerle ilgili çeşitli sorular sormak için kullanılan bir küçük dile sahiptir. Aşağıdaki sorgu türleri sağlanır:
bazel query, hedef grafiği incelemek için kullanılırbazel 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ıflama ile 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ılabilmesi ve 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 işlevlerini 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
Uzantı noktaları BlazeModule teklifleri 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'in 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. Protokol mesajlarını içeren bir dosya olarak erişilebilirler 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 yaşıyoruz. "Harici depolar", bu iki dünyayı birleştirmek 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ı depodaki 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ılmadan önce getirilmesi gerekir. Bu durumda Bazel, $OUTPUT_BASE/external/<repository name> altında bir dizin oluşturur.
Deponun getirilmesi aşağıdaki adımlarda gerçekleşir:
PackageLookupFunctionbir depoya ihtiyacı olduğunu fark eder veSkyKeyolarakRepositoryNameoluşturur. Bu işlemRepositoryLoaderFunction'ü çağırır.RepositoryLoaderFunction, isteği net olmayan nedenlerleRepositoryDelegatorFunction'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).RepositoryDelegatorFunctionİstenen depo bulunana kadar WORKSPACE dosyasının parçalarını yineleyerek getirmesi istenen depo kuralını bulur.- Depo getirme işlemini uygulayan uygun
RepositoryFunctionbulunur. Bu, deponun Starlark 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:
- İndirilen dosyalar için denetim toplamlarına göre anahtarlanmış bir önbellek vardır
(
RepositoryCache). Bu, denetim toplamının WORKSPACE dosyasında kullanılabilir olmasını gerektirir ancak bu, hermetiklik için de iyidir. Bu, hangi çalışma alanında veya çıkış tabanında çalıştıklarına bakılmaksızın aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır. $OUTPUT_BASE/externalaltı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 özellikRepositoryDelegatorFunction.DigestWriteriçinde uygulanır .--distdirkomut 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,DownloadManagertarafından uygulanır .
Bir depo indirildikten sonra içindeki yapılar kaynak yapı olarak değerlendirilir. 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 her ikisi de Guava'yı @guava// ile başlayan etiketlerle ifade eder ve bunun farklı sürümler anlamına gelmesini bekler.
Bu nedenle Bazel, harici depo etiketlerinin yeniden eşlenmesine olanak tanır. Böylece @guava// dizesi, bir ikilinin deposundaki bir Guava deposunu (ör. @guava1//) ve diğerinin deposundaki başka bir Guava deposunu (ör. @guava2//) ifade edebilir.
Alternatif olarak, bu araç elmasları birleştirmek için de kullanılabilir. Bir depo @guava1//'ya, diğeri @guava2//'ye 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 özellikleriniRuleClass.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 kapsam dışındadı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:
NativePosixFilesveNativePosixFileSystemProcessUtilsWindowsFileOperationsveWindowsFileProcessescom.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 (stdout, stderr) çiftiyle sarmalanır. Konsola yazdırılması gereken her şey bu akışlardan geçer. Ardından bu yayınlar BlazeCommandDispatcher.execExclusively()'a 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ış üretiyorsa (ö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, EventKind (hata, uyarı, bilgi ve birkaç başka) içerir ve Location (olayın gerçekleşmesine neden olan kaynak kodundaki yer) içerebilir.
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 şey bu arayüzü uygulamayabilir. Yalnızca ExtendedEventHandler tarafından önbelleğe alınanlar bu arayüzü uygular (Bu arayüzün uygulanması iyi olur ve çoğu durumda uygulanır ancak zorunlu değildir).
Terminal çıkışı çoğunlukla UiEventHandler üzerinden verilir. UiEventHandler, Bazel'in yaptığı tüm gelişmiş çıkış biçimlendirmesinden ve ilerleme 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 olur. 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. Derlemeler, dayanılabilirliğin sınırına kadar büyüdüğü için Bazel de yavaştı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 etkindir 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, kaydedebileceği 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ını alır. YAPILACAKLAR: Bu, işlemler ve devamlılık aktarma stiliyle nasıl çalışır?
Profil oluşturucu sırasıyla BlazeRuntime.initProfiler() ve BlazeRuntime.afterCommand() içinde 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 şekilde try-with-resources ifadeleriyle kullanılır.
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:
src/test/shellaltında çok ayrıntılı bir bash test çerçevesi kullanılarak uygulananlar- Java'da uygulananlar. Bunlar,
BuildIntegrationTestCasealt 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ı 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.