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 kendilerini karanlık bir ormanda, düz bir yolun kaybolduğu bir yerde bulmamaları için bu dokümanda, kod tabanına genel bir bakış sunulmaya çalışılarak üzerinde çalışmaya başlamaları kolaylaştırılmıştır.
Bazel kaynak kodunun herkese açık sürümü, github.com/bazelbuild/bazel adresindeki GitHub'da yer alır. Bu, "doğru kaynak" değildir; Google dışında kullanışlı olmayan ek işlevler içeren Google'a ait bir kaynak ağacından türetilmiştir. Uzun vadeli hedefimiz, GitHub'ı bilgi kaynağı haline getirmektir.
Katkılar, normal GitHub pull isteği mekanizması aracılığıyla kabul edilir ve bir Google çalışanı tarafından manuel olarak dahili kaynak ağacına aktarılır, ardından GitHub'a yeniden dışa aktarılır.
İstemci/sunucu mimarisi
Bazel'in büyük kısmı, derlemeler arasında RAM'de kalan bir sunucu sürecinde bulunur. Bu sayede Bazel, derlemeler arasında durumu koruyabilir.
Bu nedenle Bazel komut satırında iki tür seçenek vardır: başlangıç ve komut. Aşağıdaki gibi bir komut satırında:
bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar
Bazı seçenekler (--host_jvm_args=
), çalıştırılacak komutun adından önce, bazıları (-c opt
) sonrasındadır. İlk tür, "başlangıç seçeneği" olarak adlandırılır ve sunucu işlemini bir bütün olarak etkiler. İkinci tür ise "komut seçeneği" yalnızca tek bir komutu etkiler.
Her sunucu örneğinin tek bir ilişkili kaynak ağacı ("çalışma alanı") vardır ve her çalışma alanı genellikle tek bir etkin sunucu örneği içerir. Bu sorun, özel bir çıkış tabanı belirterek atlatılabilir (daha fazla bilgi için "Dizin düzeni" bölümüne bakın).
Bazel, aynı zamanda geçerli bir .zip dosyası olan tek bir ELF yürütülebilir dosyası olarak dağıtılır.
bazel
yazdığınızda, C++'da uygulanan yukarıdaki ELF yürütülebilir dosyası ("istemci") kontrolü alır. Aşağıdaki adımları kullanarak uygun bir sunucu işlemi oluşturur:
- Dosyanın daha önce ayıklanıp ayıklanmadığını kontrol eder. Aksi takdirde, bu işlemi yapar. Sunucunun uygulanmasının nedeni budur.
- Ç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. - Gerekirse eski sunucu işlemini sonlandırır.
- Gerekirse yeni bir sunucu işlemi başlatır
Uygun bir sunucu işlemi hazır olduktan sonra, çalıştırılması gereken komuta bir gRPC arayüzü üzerinden iletilir. Daha sonra, Bazel'in çıkışı tekrar terminale iletilir. Aynı anda yalnızca bir komut çalışabilir. Bu uygulama, parçaları C++ ve Java'da bulunan parçaların yer aldığı 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
öğelerinin yaşam döngüsü ve BlazeRuntime
'daki bazı durumlardır.
Bir komutun sonunda, Bazel sunucusu istemcinin döndürmesi gereken çıkış kodunu iletir. bazel run
komutunun uygulanması ilginç bir ayrıntıdır: Bu komutun görevi, Bazel'in yeni oluşturduğu bir şeyi çalıştırmaktır ancak terminali olmadığı için bunu sunucu sürecinden yapamaz. Bunun yerine, istemciye hangi ikili dosyayı ujexec() ile hangi bağımsız değişkenlerle çalıştırması gerektiğini söyler.
Ctrl-C tuşlarına basıldığında istemci, bu tuş kombinasyonunu gRPC bağlantısında bir İptal çağrısına dönüştürür. Bu çağrı, komutu en kısa sürede sonlandırmaya çalışır. Üçüncü Ctrl-C'den sonra istemci, bunun yerine sunucuya SIGKILL gönderir.
İstemcinin kaynak kodu src/main/cpp
altındadır ve sunucuyla iletişim kurmak için kullanılan protokol src/main/protobuf/command_server.proto
içindedir.
Sunucunun ana giriş noktası BlazeRuntime.main()
'tür ve istemciden gelen gRPC çağrıları GrpcServerImpl.run()
tarafından işlenir.
Dizin düzeni
Bazel, derleme sırasında biraz karmaşık bir dizi dizin oluşturur. Tam açıklamayı Çıkış dizini düzeninde bulabilirsiniz.
"Çalışma alanı", Bazel'in çalıştırıldığı kaynak ağacıdır. Genellikle kaynak kontrolünde kontrol ettiğiniz 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}
olur ancak --output_user_root
başlangıç seçeneği kullanılarak geçersiz kılınabilir.
"Yükleme tabanı", Bazel'in ayıklandığı yerdir. Bu işlem otomatik olarak yapılır ve her Bazel sürümü, yükleme tabanındaki sağlama toplamına göre bir alt dizin alır. Varsayılan olarak $OUTPUT_USER_ROOT/install
değerindedir ve --install_base
komut satırı seçeneği kullanılarak değiştirilebilir.
"Çıkış tabanı", belirli bir çalışma alanına ekli Bazel örneğinin yazdığı yerdir. Her çıkış tabanında, herhangi bir zamanda en fazla bir Bazel sunucu örneği çalışır. Normalde saat $OUTPUT_USER_ROOT/<checksum of the path
to the workspace>
. --output_base
başlatma seçeneği kullanılarak değiştirilebilir. Bu seçenek, diğer özelliklerin yanı sıra herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalıştırılabileceği sınırlamalarını aşmak için kullanışlıdır.
Çıkış dizini şunları içerir:
$OUTPUT_BASE/external
adresindeki getirilen harici depolar.- Mevcut derlemenin tüm kaynak kodunun sembolik bağlantılarını içeren bir dizin olan exec kökü. Adresi:
$OUTPUT_BASE/execroot
. Derleme sırasında çalışma dizini$EXECROOT/<name of main repository>
olur. Bu özelliği$EXECROOT
olarak değiştirmeyi planlıyoruz. Ancak bu, çok uyumsuz bir değişiklik olduğu için uzun vadeli bir plandır. - Derleme sırasında oluşturulan dosyalar.
Komut yürütme işlemi
Bazel sunucusu kontrolü aldıktan ve yürütmesi gereken bir komut hakkında bilgilendirildikten sonra aşağıdaki etkinlikler dizisi gerçekleşir:
BlazeCommandDispatcher
, yeni istek hakkında bilgilendirilir. Komutun çalışması için bir çalışma alanına ihtiyaç duyup duymadığına (sürüm veya yardım gibi kaynak koduyla ilgisi olmayan komutlar dışında neredeyse her komut) ve başka bir komutun çalışıp çalışmadığına karar verir.Doğru komut bulundu. Her komut
BlazeCommand
arayüzünü uygulamalı ve@Command
ek açıklamasına sahip olmalıdır (bu biraz anti-modeldir. Bir komutun ihtiyaç duyduğu tüm meta verilerinBlazeCommand
'teki yöntemlerle açıklanması iyi olur).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.Bir etkinlik otobüsü oluşturulur. Etkinlik veri yolu, derleme sırasında gerçekleşen etkinlikler için bir akıştır. Bunlardan bazıları, derlemenin nasıl gittiğini dünyaya bildirmek için Derleme Etkinliği Protokolü kapsamında Bazel'in dışına aktarılır.
Komut kontrolü alır. En ilginç komutlar, derleme, test, çalıştırma, kapsam ve benzeri işlemleri çalıştıran komutlardır: Bu işlev
BuildTool
tarafından uygulanır.Komut satırındaki hedef kalıpları grubu ayrıştırılır ve
//pkg:all
ile//pkg/...
gibi joker karakterler çözümlenir. Bu,AnalysisPhaseRunner.evaluateTargetPatterns()
üzerinde uygulandı ve Skyframe'deTargetPatternPhaseValue
olarak yeniden düzenlendi.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.
Yürütme aşaması çalıştırılır. Yani, istenen üst düzey hedefleri oluşturmak için gereken her işlemin çalıştırılması gerekir.
Komut satırı seçenekleri
Bazel çağrısı için komut satırı seçenekleri, bir OptionsParsingResult
nesnesinde açıklanır. Bu nesne de "option sınıflarından" seçeneklerin değerlerine bir eşleme içerir. "Seçenek sınıfı", OptionsBase
alt sınıfıdır ve birbirleriyle ilgili komut satırı seçeneklerini gruplandırır. Örneğin:
- Bir programlama diliyle (
CppOptions
veyaJavaOptions
) ilgili seçenekler. BunlarFragmentOptions
alt sınıfı olmalı ve sonunda birBuildOptions
nesnesine sarmalanmalıdır. - 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++ dahil etme taramasının yapılıp yapılmayacağı) yürütme aşamasında okunur ancak BuildConfiguration
o sırada kullanılamadığından bu işlem her zaman açık bağlantı gerektirir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.
UYARI: OptionsBase
ö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.
(FragmentOptions
öğesini, başka bir kullanıcının referans alma şansı bulamadan ve equals()
veya hashCode()
çağrılmadan hemen sonra değiştirmek sorun değildir.)
Bazel, seçenek sınıfları hakkında aşağıdaki yöntemlerle bilgi edinir:
- Bazıları Bazel'e (
CommonCommandOptions
) bağlıdır. - Her Bazel komutundaki @Command ek açıklamasından
ConfiguredRuleClassProvider
'ten (bunlar, ayrı programlama dilleriyle ilgili komut satırı seçenekleridir)- 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 tarafından görülen kaynak ağacı
Bazel, kaynak kodu okuyup yorumlayarak yazılım oluşturma işi yapar. Bazel'in üzerinde çalıştığı kaynak kodunun bütünlüğü "çalışma alanı" olarak adlandırılır ve depolar, paketler ve kurallar halinde yapılandırılır.
Kod depoları
"Depo", geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır ve genellikle tek bir projeyi temsil eder. Bazel'in atası Blaze, 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şılık Bazel, kaynak kodu birden fazla depoyu kapsayan projeleri destekler. Bazel'in çağrıldığı depoya "ana depo", diğer depolara ise "harici depolar" denir.
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 kodu, $OUTPUT_BASE/external
altında simge bağlantısı oluşturur veya indirilir.
Derleme çalıştırılırken kaynak ağacın tamamının bir araya getirilmesi gerekir. Bu işlem, ana depodaki her paketi $EXECROOT
ile $EXECROOT
ve harici depoları birbirine bağlayan SymlinkForest tarafından yapılır (bu hizmette external
adı verilen bir paketin ana depodan uzağa taşınması mümkün değildir).$EXECROOT/external
$EXECROOT/..
Paketler
Her depo; paketler, ilgili dosyalar koleksiyonu ve bağımlılıkların spesifikasyonundan oluşur. Bunlar, BUILD
veya BUILD.bazel
adlı bir dosyayla belirtilir. Her ikisi de varsa Bazel BUILD.bazel
dosya adını tercih eder. BUILD
dosyalarının hâlâ kabul edilmesinin nedeni, Bazel'in atası Blaze'ın bu dosya adını kullanmasıdır. Ancak özellikle dosya adlarının büyük/küçük harf duyarlı olmadığı Windows'ta yaygın olarak kullanılan bir yol segmenti olduğu ortaya çıktı.
Paketler birbirinden bağımsızdır: Bir paketin BUILD
dosyasında yapılan değişiklikler, diğer paketlerin değişmesine neden olamaz. Yinelenen glob'lar paket sınırlarında durduğundan ve dolayısıyla BUILD
dosyasının varlığı yinelemeyi durdurduğundan, BUILD
dosyalarının eklenmesi veya kaldırılması diğer paketleri _değiştirebilir_.
BUILD
dosyasının değerlendirilmesine "paket yükleme" adı verilir. PackageFactory
sınıfında uygulanır, Starlark yorumlayıcısını çağırarak çalışır ve mevcut kural sınıfları hakkında bilgi sahibi olmayı gerektirir. Paket yüklemenin sonucu bir Package
nesnesidir. Çoğunlukla bir dizeyi (hedefin adı) hedefin kendisine eşleyen bir eşlemedir.
Paket yükleme sırasında karmaşıklığın büyük bir kısmı, genelleştirmeden kaynaklanır: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine genelleştirmeleri (glob(["**/*.java"])
gibi) çalıştırabilir. Kabuğun aksine, alt dizinlere inen (ancak alt paketlere inmeyen) yinelenen genelleştirmeleri destekler. Bu, 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üreSkyframeHybridGlobber
: Skyframe kullanan ve "Skyframe'ın yeniden başlatılmasını" (aşağıda açıklanmıştır) önlemek için eski toplayıcıya geri dönen bir sürüm
Package
sınıfının kendisi, yalnızca WORKSPACE dosyasını ayrıştırmak için kullanılan ve gerçek paketler için anlamlı olmayan bazı üyeler içerir. Normal paketleri tanımlayan nesneler başka bir şeyi tanımlayan alanlar içermediğinden bu bir tasarım hatası. Bunlardan bazıları:
- Depo eşlemeleri
- Kayıtlı araç zincirleri
- Kayıtlı yürütme platformları
İdeal olarak, WORKSPACE dosyasının ayrıştırılması ile normal paketlerin ayrıştırılması arasında daha fazla ayrım olması gerekir. Böylece Package
'in her ikisinin de ihtiyaçlarını karşılaması gerekmez. Bu ikisi birbiriyle iç içe geçmiş olduğundan maalesef bunu yapmak zordur.
Etiketler, Hedefler ve Kurallar
Paketler, aşağıdaki türlere sahip hedeflerden oluşur:
- Dosyalar: Derlemenin girişi veya çıkışı olan öğeler. Bazel dilinde bunlara yapılar denir (başka bir yerde ele alınmıştır). Derleme sırasında oluşturulan dosyaların tümü hedef değildir. Bazel'in çıktısının ilişkili bir etikete sahip olmaması yaygın bir durumdur.
- 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
veyapy_library
gibi), ancak dilden bağımsız bazı türler de vardır (genrule
veyafilegroup
gibi). - 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ı, pac/kage
, BUILD
dosyasının bulunduğu dizin ve name
, paketin dizine göre dosyanın yoludur (etiket bir kaynak dosyayı ifade ediyorsa). Komut satırında bir hedeften bahsederken etiketin bazı bölümleri atlanabilir:
- Depo atlanırsa etiketin ana depoda olduğu kabul edilir.
- Paket kısmı atlanırsa (
name
veya:name
gibi) etiketin mevcut çalışma dizininin paketinde olduğu kabul 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:
- Özellikleri (ör.
srcs
,deps
): türleri, varsayılan değerleri, kısıtlamaları vb. - Her bir özelliğe bağlı yapılandırma geçişleri ve yönler (varsa)
- Kuralın uygulanması
- 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 temel aldığı değerlendirme çerçevesine Skyframe adı verilir. 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
ve adları SkyKey
olarak adlandırılır. 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, grafiği satır başına bir SkyValue
olacak şekilde döken bazel dump
--skyframe=detailed
komutunu çalıştırmaktır. Bunu çok küçük yapılarda yapmak
en iyisidir çünkü çok büyük olabiliyor.
Skyframe, com.google.devtools.build.skyframe
paketinde bulunur. Benzer şekilde adlandırılmış com.google.devtools.build.lib.skyframe
paketi, Skyframe'ın üzerine Bazel'in uygulanmasını içerir. Skyframe hakkında daha fazla bilgiye buradan ulaşabilirsiniz.
Skyframe, belirli bir SkyKey
değerini SkyValue
olarak değerlendirmek için anahtarın türüne karşılık gelen SkyFunction
işlevini çağırır. İşlev değerlendirilirken SkyFunction.Environment.getValue()
işlevinin çeşitli aşırı yüklemelerini çağırarak Skyframe'dan başka bağımlılıklar isteyebilir. Bu, bu bağımlılıkları Skyframe'ın dahili grafiğine kaydetme yan etkisine sahiptir. Böylece Skyframe, bağımlılıkları değiştiğinde işlevi yeniden değerlendirmeyi bilir. Diğer bir deyişle, Skyframe'ın önbelleğe alma ve artımlı hesaplama işlemleri SkyFunction
ve SkyValue
düzeyinde çalışır.
Bir SkyFunction
, kullanılamayan bir bağımlılık istediğinde getValue()
null döndürür. Ardından işlev, null döndürerek kontrolü Skyframe'a geri vermelidir. Skyframe, daha sonra kullanılamayan bağımlılığı değerlendirir ve işlevi baştan başlatır. Ancak bu kez getValue()
çağrısı, null olmayan bir sonuçla başarılı olur.
Bunun bir sonucu olarak, yeniden başlatmadan önce SkyFunction
içinde yapılan tüm hesaplamaların tekrarlanması gerekir. Ancak ö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:
- Yeniden başlatma sayısını sınırlamak için bağımlılıklarını gruplar halinde bildirme (
getValuesAndExceptions()
kullanarak). - Bir
SkyValue
'ü, bağımsız olarak hesaplanıp önbelleğe alınabilmeleri için farklıSkyFunction
'lar tarafından hesaplanan ayrı parçalara ayırma. Bellek kullanımını artırma potansiyeli olduğundan bu işlem stratejik bir şekilde yapılmalıdır. SkyFunction.Environment.getState()
kullanarak veya "Skyframe'ın arkasında" geçici bir statik önbelleğe sahip olarak yeniden başlatmalar arasında durumu depolama.
Temel olarak bu tür geçici çözümlere ihtiyacımız var çünkü rutin olarak yüz binlerce uçuştaki Skyframe düğümümüz var ve Java hafif iş parçacıklarını desteklemiyor.
Starlark
Starlark, kullanıcıların Bazel'i yapılandırmak ve genişletmek için kullandığı alana özgü bir dildir. Python'un çok daha az türe sahip, kontrol akışında daha fazla kısıtlamaya sahip ve en önemlisi de eşzamanlı okumaları etkinleştirmeyi garanti eden güçlü bir değişmezlik özelliğine sahip kısıtlanmış bir alt kümesi olarak tasarlanmıştır. 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 bağımsız bir Go uygulaması da burada mevcuttur. Bazel'de kullanılan Java uygulaması şu anda bir yorumcu.
Starlark aşağıdakiler dahil olmak üzere çeşitli bağlamlarda kullanılır:
BUILD
dili. Yeni kurallar burada tanımlanır. Bu bağlamda çalışan Starlark kodu yalnızcaBUILD
dosyasının ve bu dosya tarafından yüklenen.bzl
dosyalarının içeriğine erişebilir.- Kural tanımları. Yeni kurallar (ör. yeni bir dil desteği) 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).
- WORKSPACE dosyası. Harici depolar (ana kaynak ağacında olmayan kod) burada tanımlanır.
- Depo kural tanımları. Yeni harici depo türleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu, Bazel'in çalıştığı makinede rastgele kod çalıştırabilir ve çalışma alanının dışına 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 bilgiye buradan ulaşabilirsiniz.
Yükleme/analiz aşaması
Yükleme/analiz aşamasında Bazel, belirli bir kuralı oluşturmak için hangi işlemlerin gerekli olduğunu belirler. Temel birimi, "yapılandırılmış hedef"tir. Bu, oldukça mantıklı bir şekilde bir (hedef, yapılandırma) çiftidir.
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:
- Paketleri yükleme, yani
BUILD
dosyalarını onları temsil edenPackage
nesnelerine 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 kapatma işlemindeki her yapılandırılmış hedef, aşağıdan yukarıya doğru analiz edilmelidir. Yani önce yaprak düğümler, ardından komut satırındakiler analiz edilmelidir. Tek bir yapılandırılmış hedefin analizine yönelik girişler şunlardır:
- Yapılandırma. ("Bu kuralın nasıl oluşturulacağı"; örneğin, hedef platform, kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi ayarlar)
- Doğrudan bağımlılıklar. Geçiş bilgi sağlayıcıları, analiz edilen kural tarafından kullanılabilir. Bu şekilde adlandırılırlar çünkü yapılandırılmış hedefin geçişli kapanmasında, sınıf yolundaki tüm .jar dosyaları veya bir C++ ikili programına bağlanması gereken tüm .o dosyaları gibi bilgilerin "toplayıcısı" bulunur.
- Hedefin kendisi. Bu, hedefin bulunduğu paketin yüklenmesi sonucunda ortaya çıkar. Kurallar söz konusu olduğunda bu, genellikle önemli olan özelliklerini içerir.
- Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark veya Java'da olabilir. Kural olmayan tüm yapılandırılan hedefler Java'da uygulanır.
Yapılandırılmış bir hedefin analizinden elde edilen çıkış:
- Kendisine bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar erişebilir
- Oluşturabildiği yapıları ve bu yapıları oluşturan işlemleri.
Java kurallarına sunulan API, Starlark kurallarının ctx
bağımsız değişkeninin eşdeğeri olan RuleContext
bağımsız değişkenidir. API'si daha güçlüdür, ancak aynı zamanda 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()
içinde bulunur.
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 çalıştırılan bir araç ve hedef kod için aynı kod kullanıldığında ve çapraz derleme yaptığımızda veya büyük bir Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren) oluşturduğumuzda kullanışlıdır.
Kavramsal olarak yapılandırma bir BuildOptions
örneğidir. Ancak pratikte BuildOptions
, ek çeşitli işlevler sağlayan BuildConfiguration
tarafından sarmalanır. Bağımlılık grafiğinin üst kısmından alt kısmına doğru yayılır. Değişmesi durumunda 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ırmadan bir kısmına ihtiyacı olduğunda, RuleClass.Builder.requiresConfigurationFragments()
kullanılarak tanımında bu kısım belirtilmelidir. 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 ucunda 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()
bölümünde belirtilmiştir ve birRule
(geçişin gerçekleştiği) veBuildOptions
(orijinal yapılandırma) konumundan bir veya daha fazlaBuildOptions
öğesine (çıktı yapılandırması) kullanılan işlevlerdir. - 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:
- 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
- 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 (dokümanlara buradan ulaşabilirsiniz)
Geçiş bilgi 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" ifadesinin bulunmasının nedeni, genellikle yapılandırılmış bir hedefin geçişli kapatmasının bir tür toplama işlemi olmasıdır.
Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir yazışma olur (Bu API'nin doğrudan Java’nın harf çevirisinden daha Starlark tarzında olduğu düşünüldüğünde FileProvider
, FilesToRunProvider
ve RunfilesProvider
öğelerinin birleşiminden oluşan DefaultInfo
istisnadır).
Anahtarları aşağıdakilerden biridir:
- Java sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar için kullanılabilir. Bu sağlayıcılar
TransitiveInfoProvider
sınıfının alt sınıfıdır. - 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
sınıfının doğrudan alt sınıflarıdır . - Sağlayıcı sembolü. Bu,
provider()
işlevi kullanılarak Starlark'tan oluşturulabilir ve yeni sağlayıcılar oluşturmanın önerilen yoludur. Bu simge, Java'da birProvider.Key
örneğiyle temsil edilir.
Java'da uygulanan yeni sağlayıcılar BuiltinProvider
kullanılarak uygulanmalıdır.
NativeProvider
desteği sonlandırıldı (henüz kaldırmaya zamanımız olmadı) ve TransitiveInfoProvider
alt sınıflarına Starlark'tan erişilemez.
Yapılandırılmış hedefler
Yapılandırılmış hedefler RuleConfiguredTargetFactory
olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf 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:
filesToBuild
, "bu kuralın temsil ettiği dosya grubu" şeklindeki belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef komut satırında veya bir genrule'nin srcs bölümündeyken derlenen dosyalardır.- Çalıştırma dosyaları, normal ve veriler.
- Çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya grupları"dır. Bunlara BUILD'deki filegroup kuralının output_group özelliği ve Java'daki
OutputGroupInfo
sağlayıcısı kullanılarak erişilebilir.
Çalışma dosyaları
Bazı ikili dosyaların çalışması için veri dosyalarına ihtiyacı vardır. Giriş dosyalarına ihtiyaç duyan testler buna örnek gösterilebilir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "Çalışma dosyası ağacı", belirli bir ikili dosyanın veri dosyalarının dizin ağacıdır. Dosya sisteminde, çıkış ağaçları kaynağındaki dosyaları işaret eden ayrı simge bağlantıları içeren bir simge bağlantısı ağacı olarak oluşturulur.
Bir çalışma dosyası grubu, Runfiles
örneği olarak temsil edilir. Kavramsal olarak, runfiles ağacındaki bir dosyanın yolundan, dosyayı temsil eden Artifact
örneğine giden bir haritadır. İki nedenden dolayı tek bir Map
parametresinden biraz daha karmaşıktır:
- Bir dosyanın runfiles yolu çoğu zaman execpath ile aynıdır. Bu yöntemi, RAM'den tasarruf etmek için kullanırız.
- Runfiles ağaçlarında, temsil edilmesi gereken çeşitli eski giriş türleri vardır.
Runfile'ler RunfilesProvider
kullanılarak toplanır: Bu sınıfın bir örneği, yapılandırılmış bir hedefin (ör. kitaplık) runfile'lerini ve onun geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir küme gibi toplanır (aslında, arka planda iç içe yerleştirilmiş kümeler kullanılarak uygulanır): Her hedef, bağımlılıklarının runfile'lerini birleştirir, kendi runfile'lerini ekler ve elde edilen kümeyi bağımlılık grafiğinde yukarı doğru gönderir. Bir RunfilesProvider
örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlar için, diğeri de gelen bağımlılık türleri için olmak üzere iki Runfiles
örneği içerir. Bunun nedeni, bir hedefin 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, Runfiles 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.
- Runfiles arabulucu. Runfiles ağacının var olması için sembolik bağlantı ağacını ve sembolik bağlantıların işaret ettiği yapıyı derlemeniz gerekir. Bağımlılık kenarlarının sayısını azaltmak için runfiles aracısı, bunların tümünü temsil etmek üzere kullanılabilir.
RunfilesSupport
nesnesinin temsil ettiği çalışma dosyalarını içeren ikili dosyayı çalıştırmak için komut satırı bağımsız değişkenleri.
Yönler
Yönler, "hesaplamayı bağımlılık grafiğinden aşağıya 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'da SkyValue
olarak temsil edilirler ve yapılandırılmaları, yapılandırılmış hedeflerin yapılandırılmasına çok benzer: RuleContext
'ye erişimi olan ConfiguredAspectFactory
adlı bir fabrika sınıfları vardır ancak yapılandırılmış hedef fabrikalarının aksine, bağlı oldukları yapılandırılmış hedef ve sağlayıcıları hakkında da bilgi sahibidirler.
Bağımlılık grafiğinde aşağı yayılan yönler, her bir ö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:
AspectClass
, özelliğin uygulanmasıdır. Java'da (bu durumda bir alt sınıftır) veya Starlark'ta (bu durumdaStarlarkAspectClass
öğesinin bir örneğidir) olabilir.RuleConfiguredTargetFactory
koduna benzer.AspectDefinition
, boyutun tanımıdır; gerektirdiği sağlayıcıları, sağladığı sağlayıcıları içerir ve uygunAspectClass
örneği gibi uygulamaya dair bir referans içerir.RuleClass
ile benzerdir.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.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.RuleAspect
, belirli bir kuralın hangi yönlerinin dağıtılacağını belirleyen işlevdir. Bu birRule
->Aspect
işlevidir.
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 yönü, (proto_library
kuralı + Java proto yönü) çiftine bağlanmak ister.
Yönlere ilişkin özelliklerin karmaşıklığı AspectCollection
sınıfında yakalanır.
Platformlar ve araç zincirleri
Bazel, çok platformlu derlemeleri destekler. Yani, derleme işlemlerinin çalıştırıldığı birden fazla mimari ve kodun derlendiği birden fazla mimari olabilir. Bu mimarilere Bazel dilinde platform adı verilir (tam dokümanlar burada).
Platform, kısıt ayarları ("CPU mimarisi" kavramı gibi) ile kısıt değerleri ("x86_64" gibi belirli bir CPU gibi) arasındaki bir anahtar/değer eşlemesi ile açıklanır. @platforms
deposunda, en sık kullanılan kısıtlama ayarlarını ve değerlerini içeren bir "sözlüğü" bulabilirsiniz.
Araçlar zinciri kavramı, derlemenin hangi platformlarda çalıştığına ve hangi platformların hedeflendiğine bağlı olarak farklı derleyiciler 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, belirlenen yürütme ve hedef platforma göre kullanılan C++ derleyiciyi belirlemelidir (araç zincirleriyle ilgili dokümanlar burada).
Bunun için araç zincirleri, destekledikleri yürütme ve hedef platform kısıtlamaları kümesiyle ek açıklamaya sahiptir. Bunu yapabilmek 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ını tanımlayan ve ne tür bir araç zinciri (ör. C++ veya Java) olduğunu belirten bir
toolchain()
kuralı (ikincisitoolchain_type()
kuralı ile gösterilir) - 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 yöntemlerden biriyle belirtilir:
- WORKSPACE dosyasında
register_execution_platforms()
işlevini kullanarak - Komut satırında --extra_execution_platforms komut satırı seçeneğini kullanarak
Kullanılabilir yürütme platformları grubu RegisteredExecutionPlatformsFunction
içinde hesaplanır .
Yapılandırılmış bir hedefin hedef platformu PlatformOptions.computeTargetPlatform()
tarafından belirlenir . Bu, nihayetinde birden fazla hedef platformu desteklemek istediğimiz için platformların listesidir ancak henüz uygulanmamıştır.
Yapılandırılmış bir hedef için kullanılacak araç zinciri grubu ToolchainResolutionFunction
tarafından belirlenir. Aşağıdakilerin işlevidir:
- Kayıtlı araç zincirleri grubu (WORKSPACE dosyasında ve yapılandırmada)
- İstenen yürütme ve hedef platformlar (yapılandırmada)
- Yapılandırılmış hedef tarafından gereken araç zinciri türleri grubu (
UnloadedToolchainContextKey)
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.
Ardından araç zincirleri ResolvedToolchainContext.load()
kullanılarak yüklenir ve bunları isteyen yapılandırılmış hedefin uygulaması tarafından kullanılır.
Ayrıca, tek bir "ana makine" yapılandırması ve hedef yapılandırmaların --cpu
gibi çeşitli yapılandırma işaretleriyle temsil edildiği eski bir sistemimiz de var. Yukarıdaki sisteme kademeli olarak geçiş yapıyoruz. Kullanıcıların eski yapılandırma değerlerini kullandığı durumları ele almak için eski işaretler ile yeni stil platform kısıtlamaları arasında çeviri yapmak üzere platform eşlemeleri uyguladık.
Kodları PlatformMappingFunction
dilindedir ve Starlark dışında bir "küçük dil" kullanır.
Sınırlamalar
Bazen kullanıcılar bir hedefi yalnızca birkaç platformla uyumlu olarak tanımlamak isteyebilir. 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. Kullanımdan kaldırılmaktadırlar ve Bazel'da kullanılamazlar, ancak kaynak kodunda buna referanslar olabilir. Bunu yöneten özelliğe constraints=
denir .
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.
Bir kural için desteklenen ortamlar çeşitli şekillerde belirtilebilir:
restricted_to=
özelliği aracılığıyla. Bu, spesifikasyonun en doğrudan biçimidir; kuralın bu grup için desteklediği tam ortam grubunu belirtir.compatible_with=
özelliği aracılığıyla. Bu, varsayılan olarak desteklenen "standart" ortamlara ek olarak bir kuralın desteklediği ortamları belirtir.- Paket düzeyindeki
default_restricted_to=
vedefault_compatible_with=
özellikleri aracılığıyla 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. Bir ortam grubunun tanımı,restricted_to=
/environment()
özellikleri tarafından başka şekilde belirtilmediği takdirde bu ortamlardan hangilerinin "varsayılan" olarak destekleneceğini içerir. Bu tür özellikleri olmayan bir kural, tüm varsayılanları devralır.- Kural sınıfı varsayılan ayarı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ü RuleContextConstraintSemantics
ve TopLevelConstraintSemantics
'te uygulanmaktadır.
Platform kısıtlamaları
Bir hedefin hangi platformlarla uyumlu olduğunu açıklamanın mevcut "resmi" yolu, araç zincirlerini ve platformları tanımlamak için kullanılan aynı kısıtlamaları kullanmaktır. Bu özellik, #10945 numaralı pull isteğinde inceleniyor.
Görünürlük
Çok sayıda geliştiricinin bulunduğu büyük bir kod tabanında çalışıyorsanız (Google gibi), diğer herkesin kodunuza keyfi olarak bağımlı olmasını önlemek için dikkatli olmanız gerekir. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar uygulama ayrıntıları olarak kabul ettiğiniz davranışlar kullanmaya başlar.
Bazel, bunu görünürlük adlı mekanizmayla destekler: Belirli bir hedefe yalnızca görünürlük özelliğini kullanarak güvenilebileceğini belirtebilirsiniz. Bu özellik biraz özeldir çünkü etiketlerin listesi tutulsa da bu etiketler belirli bir hedefe 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 beyanını temsil eder. Sabit (tamamen herkese açık veya tamamen gizli) ya da etiket listesi olabilir.- Etiketler, paket gruplarına (önceden tanımlanmış paket listesi) doğrudan paketlere (
//pkg:__pkg__
) veya paketlerin alt ağaçlarına (//pkg:__subpackages__
) başvurabilir. Bu,//pkg:*
veya//pkg/...
kullanan komut satırı söz diziminden farklıdır. - Paket grupları kendi hedefleri (
PackageGroup
) ve yapılandırılmış hedef (PackageGroupConfiguredTarget
) olarak uygulanır. İstediğimizde bunları basit kurallarla değiştirebiliriz. Mantıkları şu öğelerin yardımıyla uygulanır://pkg/...
gibi tek bir kalıba karşılık gelenPackageSpecification
; tek birpackage_group
'ınpackages
özelliğine karşılık gelenPackageGroupContents
ve birpackage_group
ile onun geçişliincludes
özelliğini toplayanPackageSpecificationProvider
. - 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ış hedefler genellikle bağımlılıklarından bir dosya grubu toplar, kendi dosyalarını ekler ve toplanan grubu, kendisine bağlı olan yapılandırılmış hedeflerin de aynısını yapabilmesi için geçişli bir bilgi sağlayıcısına sarar. Örnekler:
- Derleme için kullanılan C++ üstbilgi dosyaları
- Bir
cc_library
nesnesinin geçişli kapatmasını temsil eden nesne dosyaları - Bir Java kuralının derlenmesi veya çalıştırılması için sınıf yolu klasöründe bulunması gereken .jar dosyası grubu
- Bir Python kuralının geçişli kapatma kümesinde bulunan Python dosyası grubu
Bunu, örneğin List
veya Set
kullanarak saf bir şekilde yaparsak dörtlü bir bellek kullanımıyla karşılaşırız: N kural zinciri varsa ve her kural bir dosya ekliyorsa 1+2+...+N koleksiyon üyesi olur.
Bu sorunun üstesinden gelmek için NestedSet
kavramını geliştirdik. 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 yineleme sırası (NestedSet.Order
) tanımlarız: ön sipariş, son sipariş, topolojik (bir düğüm her zaman üst öğelerinden sonra gelir) ve "önemli değil ama her seferinde aynı olması gerekir".
Aynı veri yapısı Starlark'ta depset
olarak adlandırılır.
Yapılar ve İşlemler
Gerçek derleme, kullanıcının istediği çıkışı üretmek için çalıştırılması gereken bir dizi komuttan oluşur. Komutlar Action
sınıfının örnekleri, dosyalar ise Artifact
sınıfının örnekleri olarak 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ürde olur: kaynak yapılar (Bazel yürütmeye başlamadan önce kullanılabilenler) ve türetilen yapılar (derlenmesi gerekenler). Türetilmiş yapı taşları da birden fazla türde olabilir:
- **Normal eserler. **Bu dosyalar, kısayol olarak mtime kullanılarak sağlama toplamalarının hesaplanması yoluyla güncel olup olmadığı açısından kontrol edilir. ctime değişmemişse dosyanın sağlama toplaması hesaplanmaz.
- Çö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.
- 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. - Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler, yeniden derlemeyi tetiklemez. Bu, yalnızca derleme damgası bilgileri için kullanılır: Mevcut saat değiştiği için yeniden derleme yapmak istemeyiz.
Kaynak yapıların ağaç yapıları veya çözülmemiş sembolik bağlantı yapıları olmaması için temel bir neden yoktur. Bunun nedeni, henüz bu özelliği uygulamamış olmamızdır (Ancak uygulamamız gerekir. BUILD
dosyasında bir kaynak dizine referans vermek, Bazel'de bilinen ve uzun süredir devam eden birkaç yanlışlık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1
JVM mülkü tarafından etkinleştirilen ve bu tür durumlarda işe yarayan bir uygulamamız var).
Kayda değer bir Artifact
türü aracıdır. Bunlar, MiddlemanAction
işlevinin çıkışları olan Artifact
örnekleriyle gösterilir. Bunlar, bazı durumları özel olarak belirtmek için kullanılır:
- Toplanan aracılar, yapıları birlikte gruplandırmak için kullanılır. Bu, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarı yerine yalnızca N+M (iç içe yerleştirilmiş kümelerle değiştirilir) bağımlılık kenarı olmasını sağlar.
- Bağımlılık aracılarını programlamak, bir işlemin diğerinden önce çalışması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ç duyduğu ortam ve ürettiği çıkışlar olarak anlaşılır. Bir işlemin açıklamasının ana bileşenleri şunlardır:
- Çalıştırılması gereken komut satırı
- İhtiyaç duyduğu giriş yapıları
- Ayarlanması gereken ortam değişkenleri
- Çalıştırılması gereken ortamı (platform gibi) açıklayan ek açıklamalar \
Bazel'in bildiği içeriğe sahip bir dosya yazmak gibi birkaç özel durum daha vardır. Bunlar AbstractAction
sınıfının alt sınıfıdır. Java ve C++'ın kendi işlem türleri (JavaCompileAction
, CppCompileAction
ve CppLinkAction
) olsa da işlemlerin çoğu SpawnAction
veya StarlarkAction
'dır (aynı şekilde, muhtemelen ayrı sınıflar da olmamalıdır).
Sonunda her şeyi SpawnAction
'e taşımak istiyoruz; JavaCompileAction
oldukça yakın, ancak C++ .d dosyalarını ayrıştırması ve dahil etme taraması nedeniyle biraz özel bir durum.
İşlem grafiği çoğunlukla Skyframe grafiğine "yerleştirilir": Kavramsal olarak, bir işlemin yürütülmesi ActionExecutionFunction
çağrısı olarak temsil edilir. Bir işlem grafiği bağımlılık kenarından Skyframe bağımlılık kenarına eşleme, ActionExecutionFunction.getInputDeps()
ve Artifact.key()
içinde açıklanmıştır ve Skyframe kenarlarının sayısını düşük tutmak için birkaç optimizasyon vardır:
- Türetilen yapıların kendi
SkyValue
'leri yoktur. Bunun yerine,Artifact.getGeneratingActionKey()
, onu oluşturan işlemin anahtarını bulmak için kullanılır. - İç içe yerleştirilmiş setlerin 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ı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, RAM'in bir kısmının kullanılmasına neden olur: Paylaşılan bir işlemin her örneğinin bellekte ayrı olarak depolanması gerekir.
İki işlem aynı çıkış dosyasını oluşturuyorsa tam olarak aynı olmalıdır: Aynı girişlere, aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu eşdeğerlik ilişkisi Actions.canBeShared()
içinde uygulanır ve her işleme bakılarak analiz ile yürütme aşamaları arasında doğrulanır.
Bu işlem SkyframeActionExecutor.findAndStoreArtifactConflicts()
içinde uygulanır ve Bazel'de derlemenin "evrensel" bir görünümünü gerektiren az sayıdaki yerden biridir.
Yürütme aşaması
Bazel, bu aşamada çıkış üreten komutlar gibi derleme işlemlerini başlatır.
Bazel, analiz aşamasından sonra ilk olarak hangi yapıların oluşturulması gerektiğini belirler. Bunun mantığı TopLevelArtifactHelper
içinde kodlanmıştır; kabaca söylemek gerekirse, komut satırındaki yapılandırılmış hedeflerin filesToBuild
'ı ve "bu hedef komut satırındaysa bu yapıları derle" ifadesini açıkça belirtmek için özel bir çıkış grubunun içeriğidir.
Sonraki adım, yürütme kökünü oluşturmaktır. Bazel, kaynak paketleri dosya sistemindeki farklı konumlardan (--package_path
) okuma seçeneğine sahip olduğundan, yerel olarak yürütülen işlemlere tam bir kaynak ağacı sağlaması gerekir. 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 bir durumdu)
- Bir işlem uzaktan çalıştırıldığında yerel olarak çalıştırıldığından farklı komut satırları oluşur.
- Kullanılan araca özel bir komut satırı dönüşümü gerektirir (Java sınıf yolları ile C++ dahil etme yolları arasındaki farkı göz önünde bulundurun)
- Bir işlemin komut satırı değiştirildiğinde işlem önbelleği girişi geçersiz kılınır
--package_path
yavaş yavaş kullanımdan kaldırılıyor
Ardından Bazel, işlem grafiğini (işlemlerden ve bunların giriş ve çıkış yapılarından oluşan iki parçalı, yönlendirilmiş grafik) taramaya ve işlemleri çalıştırmaya başlar.
Her işlemin yürütülmesi, SkyValue
sınıfının ActionExecutionValue
örneğiyle temsil edilir.
Bir iş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 önbelleklerini 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 isabetlerini alabileceği anlamına gelir.
Bu önbellekte, ActionCacheChecker.getTokenIfNeedToExecute()
yöntemi kullanılarak isabet olup olmadığı kontrol edilir .
Adının aksine, türetilmiş bir eserin izlediği yoldan onu oluşturan eyleme kadar uzanan bir haritadır. İşlem şu şekilde açıklanır:
- Giriş ve çıkış dosyası grubu ve bunların sağlama toplamı
- "İş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 ve önbelleğe çok fazla gitmemek için geçişli karma oluşturma işlemleri kullanan, son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" de vardır.
Giriş keşfi ve giriş budama
Bazı işlemler sadece bir dizi girdiye 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ülürken bazı dosyaların kullanılmadığını fark edebilir. C++ ürününde buna ".d dosyaları" adı verilir. Derleyici, olaydan sonra hangi başlık dosyalarının kullanıldığını söyler. Bazel, Maket'ten daha kötü bir artışın utanç duyma ihtimalini ortadan kaldırmak için bu bilgiden yararlanır. Bu, derleyiciye dayandığı için dahil etme tarayıcısına kıyasla daha iyi bir tahmin sunar.
Bunlar, İşlem'deki yöntemler kullanılarak uygulanır:
Action.discoverInputs()
çağrıldı. Zorunlu olduğu belirlenen iç içe yerleştirilmiş bir yapı grubunu döndürür. İş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.- İşlem,
Action.execute()
çağrısı yapılarak gerçekleştirilir. Action.execute()
programının sonunda işlemAction.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.
Bir işlem önbelleği yeni bir işlem örneğinde (ör. sunucu yeniden başlatıldıktan sonra oluşturulan) bir isabet döndürdüğünde Bazel, giriş kümesinin daha önce yapılan giriş keşfi ve budama işleminin sonucunu yansıtması için updateInputs()
'yi kendisi çağırır.
Starlark işlemleri, ctx.actions.run()
işlevinin unused_inputs_list=
bağımsız değişkenini kullanarak bazı girişleri kullanılmamış olarak beyan etme olanağı sunar.
İş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 türde korumalı alanlarda veya uzaktan çalıştırılabilir. Bunu somutlaştıran kavrama ActionContext
(veya Strategy
, çünkü yeniden adlandırma işleminin yarısını başarıyla tamamladık...) denir.
Bir işlem bağlamının yaşam döngüsü aşağıdaki gibidir:
- Yürütme aşaması başladığında
BlazeModule
örneklerine hangi işlem bağlamlarına sahip oldukları sorulur. Bu,ExecutionTool
sınıfının kurucusunda gerçekleşir. İşlem bağlamı türleri,ActionContext
alt arayüzünü ifade eden ve işlem bağlamının uygulaması gereken arayüzü ifade eden JavaClass
örneğiyle tanımlanır. - Uygun işlem bağlamı, mevcut olanlar arasından seçilir ve
ActionExecutionContext
veBlazeExecutor
özelliklerine yönlendirilir . - İşlemler,
ActionExecutionContext.getContext()
veBlazeExecutor.getStrategy()
kullanarak bağlam ister (bunu yapmanın tek bir yolu olmalıdır…)
Stratejiler, işlerini yapmak için diğer stratejileri çağırabilir. Bu, örneğin, hem yerel hem de uzaktan işlem başlatan ve ardından hangisi önce biterse onu kullanan dinamik stratejide kullanılır.
Kalıcı işleyici süreçlerini (WorkerSpawnStrategy
) uygulayan stratejiler dikkate değerdir. Buradaki fikir, bazı araçların uzun bir başlatma süresine sahip olması ve bu nedenle her işlem için yeni bir işlem başlatmak yerine işlemler arasında yeniden kullanılması gerektiğidir (Bazel, işleyici sürecinin bağımsız istekler arasında gözlemlenebilir durum taşımadığına dair sözünü güvence olarak kullandığından bu, olası bir doğruluk sorununu temsil eder).
Araç değişirse işleyici işleminin yeniden başlatılması gerekir. Bir çalışanın yeniden kullanılıp kullanılamayacağı, WorkerFilesHash
kullanılarak kullanılan araç için bir sağlama toplamı hesaplanarak belirlenir. Bu, işlemin hangi girişlerinin aracın bir bölümünü temsil ettiğini ve hangilerinin girişleri temsil ettiğini bilmeye dayanır. Bu, işlemin yaratıcısı tarafından belirlenir: Spawn.getToolFiles()
ve Spawn
'un çalıştırma dosyaları aracın bir parçası olarak sayılır.
Stratejiler (veya işlem bağlamları) hakkında daha fazla bilgi:
- İşlem çalıştırmayla ilgili çeşitli stratejiler hakkında bilgi edinmek için burayı ziyaret edin.
- Bir işlemi hem yerel hem de uzaktan çalıştırarak hangisinin daha önce biteceğini görmek için kullandığımız dinamik strateji hakkında bilgi edinmek için burayı ziyaret edebilirsiniz.
- İşlemleri yerel olarak yürütmenin incelikleri hakkında bilgiye buradan ulaşabilirsiniz.
Yerel kaynak yöneticisi
Bazel, birçok işlemi paralel olarak çalıştırabilir. Paralel olarak çalıştırılması gereken yerel işlemlerin sayısı işlemden işleme değişir: Bir işlemin gerektirdiği kaynaklar ne kadar fazlaysa yerel makinenin aşırı yüklenmesini önlemek için aynı anda o kadar az örnek çalışmalıdır.
Bu, ResourceManager
sınıfında uygulanır: Her işlemin, gerektirdiği yerel kaynakların tahmini bir ResourceSet
örneği (CPU ve RAM) biçiminde ek açıklamayla belirtilmesi gerekir. Ardından işlem bağlamları yerel kaynaklar gerektiren bir işlem yaptığında ResourceManager.acquireResources()
işlevini çağırır ve gerekli kaynaklar kullanılabilene kadar engellenir.
Yerel kaynak yönetiminin daha ayrıntılı 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üretilmiş yapıların konumu genellikle aşağıdaki gibidir:
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
Belirli bir yapılandırmayla ilişkilendirilen dizinin adı nasıl belirlenir? Çakışan istenen iki özellik vardır:
- 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ı").
- İ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.
Yapılandırma kısaltma sorunuyla benzerlikler gösteren bu sorunu çözmenin temel bir yolunu henüz bulamadık. Seçenekler hakkında daha uzun bir açıklamayı burada bulabilirsiniz. Başlıca sorunlu alanlar, Starlark kuralları (yazarlar genellikle Bazel'i tam olarak bilmezler) ve özelliklerdir. Bunlar, "aynı" çıktı dosyasını oluşturabilecek şeylerin alanına yeni bir boyut katar.
Mevcut yaklaşım, yapılandırmanın yol segmentinin, Java'da uygulanan yapılandırma geçişlerinin işlem çakışmalarına yol açmaması için çeşitli sonekler eklenmiş şekilde <CPU>-<compilation mode>
olmasıdır. 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 bölümünü eklemesine dayanır.
Testler
Bazel, test çalıştırmak için zengin desteğe sahiptir. Şunları destekler:
- Testleri uzaktan çalıştırma (uzak yürütme arka ucu mevcutsa)
- Testleri paralel olarak birden çok kez çalıştı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, TestProvider'a sahip olan ve testin nasıl çalıştırılması gerektiğini açıklayan normal yapılandırılmış hedeflerdir:
- Yapısı sonucunda testin çalıştırıldığı yapılar. Bu, serileştirilmiş bir
TestResultData
mesajı içeren bir "önbelleğe alma durumu" dosyasıdır. - Testin kaç kez çalıştırılması gerektiği
- Testin bölünmesi gereken parça sayısı
- Testin nasıl çalıştırılması gerektiğiyle ilgili bazı parametreler (test zaman aşımı gibi)
Hangi testlerin çalıştırılacağını belirleme
Hangi testlerin çalıştırılacağını belirlemek ayrıntılı bir süreçtir.
İlk olarak, hedef kalıp ayrıştırma sırasında test paketleri yinelemeli olarak genişletilir. Genişletme işlemi TestsForTargetPatternFunction
üzerinde uygulanır. Biraz şaşırtıcı olan bir nokta, bir test paketinde test belirtilmemişse paketteki her testin kastedildiğidir. Bu işlem, Package.beforeBuild()
ürününde test paketi kurallarını test etmek için $implicit_tests
adlı örtülü bir özellik eklenerek 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. Filtrelenebilir 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 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. Bu sayede ExecutionTool
hangi testlerin çalıştırılacağını bilir.
Bu ayrıntılı sürece biraz şeffaflık kazandırmak için, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırılacağını öğrenmek amacıyla tests()
sorgu operatörü (TestsFunction
uygulamasında uygulanmıştır) kullanılabilir. Ne yazık ki yeniden uygulandığı için muhtemelen yukarıdakilerden birçok açıdan farklıdır.
Test çalıştırma
Testler, önbellek durumu yapılarını isteyerek çalıştırılır. Bu, bir TestRunnerAction
'ün yürütülmesine neden olur. Bu TestRunnerAction
, testi istenen şekilde çalıştıran --test_strategy
komut satırı seçeneği tarafından seçilen TestActionContext
'ı çağırır.
Testler, kendilerinden ne beklendiğini belirtmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre çalıştırılır. Bazel'in testlerden ne beklediğini ve testlerin Bazel'den ne beklediğini ayrıntılı olarak burada bulabilirsiniz. En basit haliyle, 0 çıkış kodu başarı, diğer tüm çıkış kodları ise başarısızlık anlamına gelir.
Her test işlemi, önbellek durum dosyasına ek olarak bir dizi başka dosya da oluşturur. Bunlar, hedef yapılandırma çıkış dizininin testlogs
adlı alt dizini olan "test günlük dizinine" yerleştirilir:
test.xml
: Test bölümündeki test durumlarını ayrıntılı olarak açıklayan JUnit tarzı bir XML dosyasıtest.log
, testin konsol çıkışı. stdout ve stderr ayrılmış değildir.test.outputs
, "tanımlanmamış çıkış dizini"dir. Terminale yazdıklarının yanı sıra dosya da yayınlamak isteyen testler tarafından kullanılır.
Test yürütme sırasında, normal hedefleri oluştururken yapılamayan iki işlem yapılabilir: özel test yürütme ve çıkış aktarımı.
Bazı testlerin, diğer testlerle paralel olarak değil, özel modda çalıştırılması gerekir. Bu, test kuralına tags=["exclusive"]
ekleyerek veya testi --test_strategy=exclusive
ile çalıştırarak elde edilebilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısı tarafından 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ın toplanması için her test yürütme, collect_coverage.sh
adlı bir komut dosyasına sarmalanır .
Bu komut dosyası, kapsam toplamayı etkinleştirmek ve kapsam dosyalarının kapsam çalışma zamanları tarafından nereye yazılacağını belirlemek için testin ortamını oluşturur. Ardından testi çalıştırır. Bir testin kendisi birden fazla alt işlem çalıştırabilir ve birden fazla farklı programlama dilinde yazılmış parçalardan (ayrı kapsam toplama çalışma zamanlarıyla) oluşabilir. Sarmalayıcı komut dosyası, gerekirse ortaya çıkan dosyaları LCOV biçimine dönüştürmekten ve bunları tek bir dosyada birleştirmekten sorumludur.
collect_coverage.sh
'ün araya girmesi test stratejileri tarafından yapılır ve collect_coverage.sh
'ün testin girişlerinde olması gerekir. Bu, yapılandırma işareti --coverage_support
değerine çözülen :coverage_support
gizli özelliğiyle yapılır (TestConfiguration.TestOptions.coverageSupport
bölümüne bakın).
Bazı diller çevrimdışı enstrü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 kütüphanenin, ikili programın veya testin kapsamıdır. Bu çözümün çözdüğü sorun, bir ikili program için test kapsamını hesaplamak istediğinizde tüm testlerin kapsamını birleştirmenin yeterli olmamasıdır. Çünkü ikili dosyada, herhangi bir teste bağlı olmayan kod olabilir. 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 . --nobuild_tests_only
işaretini Bazel'e iletirseniz testlerin yanı sıra ikili dosyalar ve kitaplıklar için de oluşturulur.
Temel kapsam şu anda bozuk.
Her kural için kapsam toplama amacıyla iki dosya grubunu izleriz: enstrümante edilmiş dosya grubu ve enstrümantasyon meta veri dosyası grubu.
Enstrümante edilmiş dosya grubu, enstrümante edilecek bir dosya grubudur. Çevrimiçi kapsam çalışma zamanlarında, hangi dosyaların enstrümante edileceğine karar vermek için çalışma zamanında kullanılabilir. Ayrıca, temel kapsamı uygulamak için de kullanılır.
Enstrümantasyon meta veri dosyası grubu, bir testin Bazel'in gerektirdiği LCOV dosyalarını oluşturmak için ihtiyaç duyduğu ek dosya grubudur. Pratikte 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ş grubuna eklenir.
Kapsamın toplanıp toplanmadığı BuildConfiguration
içinde saklanır. Test işlemini ve işlem grafiğini bu bite göre değiştirmenin kolay bir yolu olduğu için bu kullanışlıdır ancak bu bit değiştirilirse tüm hedeflerin yeniden analiz edilmesi gerektiği anlamına da gelir (C++ gibi bazı diller, kapsam toplayabilen kod yayınlamak için farklı derleyici seçenekleri gerektirir. Bu durumda zaten yeniden analiz yapılması gerektiğinden bu sorun biraz hafifletilir).
Kapsam destek dosyaları, örtülü bağımlılıktaki etiketlerle temel alınır. Böylece, çağrı politikası tarafından geçersiz kılınabilirler. Bu sayede, Bazel'ın farklı sürümleri arasında farklılıklar bulunabilir. İ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 işlem CoverageReportActionFactory
tarafından yönetilir ve BuildView.createResult()
çağrısından alını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ırbazel cquery
, yapılandırılmış hedef grafiği incelemek için kullanılırbazel 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. Sorgu sonuçlarını akış olarak yayınlamaya izin vermek için, sonuçları bir veri yapısında toplamak yerine QueryFunction
işlevine bir query2.engine.Callback
iletilir. QueryFunction
işlevi, döndürmek istediği sonuçlar için bu işlevi çağırır.
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. Genelde, bir etiket kullanmak ve karmaşık bilgileri bu etiketle kurala eklemek geçici çözüm olarak kullanılır. Bu geçici çözüm pek tatmin edici değil ve bu şartın kaldırılması çok iyi olur.
Modül sistemi
Bazel'e modüller ekleyerek genişletilebilir. Her modül BlazeModule
alt sınıfı olmalıdır (Bazel eskiden Blaze olarak biliniyordu. Bu geçmişe ait bir kalıntıdır) ve bir komut yürütülürken çeşitli etkinlikler hakkında bilgi verilir.
Bunlar çoğunlukla, yalnızca Bazel'in bazı sürümlerinin (Google'da kullandığımız sürüm gibi) ihtiyaç duyduğu çeşitli "temel olmayan" işlev parçalarını uygulamak için kullanılır:
- Uzaktan yürütme sistemlerine yönelik arayüzler
- Yeni komutlar
BlazeModule
tarafından sunulan uzantı noktaları grubu rastgele. İyi tasarım ilkelerine örnek olarak kullanmayın.
Etkinlik otobüsü
BlazeModules'in Bazel'in geri kalanıyla iletişim kurmasının ana yolu bir etkinlik veri yoludur (EventBus
): Her derleme için yeni bir örnek oluşturulur, Bazel'in çeşitli bölümleri buraya etkinlik gönderebilir ve modüller ilgilendikleri etkinlikler için dinleyici kaydedebilir. Örneğin, aşağıdakiler etkinlik olarak temsil edilir:
- Oluşturulacak derleme hedeflerinin listesi belirlendi
(
TargetParsingCompleteEvent
) - Üst düzey yapılandırmalar belirlendi (
BuildConfigurationEvent
) - Hedef oluşturuldu (başarılı veya başarısız) (
TargetCompleteEvent
) - Bir test çalıştırıldı (
TestAttempt
,TestSummary
)
Bu 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ı içeren bir dosya olarak erişilebilir veya Bazel, etkinlikleri yayınlamak için bir sunucuya (Derleme Etkinliği Hizmeti olarak adlandırılır) bağlanabilir.
Bu işlem, build.lib.buildeventservice
ve build.lib.buildeventstream
Java paketlerinde uygulanır.
Harici depolar
Bazel başlangıçta bir monorepo'da (yapılması gereken her şeyi içeren tek bir kaynak ağacında) kullanılmak üzere tasarlanmıştı. Ancak Bazel bunun her zaman geçerli olmadığı bir dünyada yaşıyor. "Harici depolar", bu iki dünya arasında köprü oluşturmak için kullanılan bir soyutlamadır: Derleme için gerekli olan ancak ana kaynak ağacında bulunmayan kodu temsil eder.
WORKSPACE dosyası
Harici depo grubu, WORKSPACE dosyasının ayrıştırılmasıyla belirlenir. Örneğin, şöyle bir beyan:
local_repository(name="foo", path="/foo/bar")
@foo
adlı depoda sonuçlar kullanılabilir. Bu durum karmaşık hale gelir. Starlark dosyalarında yeni depo kuralları tanımlanabilir. Bu kurallar daha sonra yeni Starlark kodunu yüklemek için kullanılabilir. Bu kod, yeni depo kuralları tanımlamak için kullanılabilir.
Bu durumu ele almak için WORKSPACE dosyasının (WorkspaceFileFunction
içindeki) ayrıştırılması, load()
ifadeleriyle açıklanan parçalara ayrılmıştır. Parça dizini WorkspaceFileKey.getIndex()
ile gösterilir ve X dizine kadar WorkspaceFileFunction
hesaplamak, X. load()
ifadesine kadar değerlendirmek anlamına gelir.
Depoları getirme
Deponun kodu Bazel'in kullanımına sunulmadan önce getirilmelidir. Bu durumda Bazel, $OUTPUT_BASE/external/<repository name>
altında bir dizin oluşturur.
Depoyu getirme işlemi aşağıdaki adımlarda gerçekleşir:
PackageLookupFunction
, bir depoya ihtiyacı olduğunu fark eder veSkyKey
olarakRepositoryName
oluşturur. Bu,RepositoryLoaderFunction
'ı çağırır.RepositoryLoaderFunction
, isteği belirsiz nedenlerleRepositoryDelegatorFunction
'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)RepositoryDelegatorFunction
, istenen depo bulunana kadar WORKSPACE dosyasının parçalarını iterleyerek getirmesi istenen depo kuralını bulur- Depo getirmeyi uygulayan uygun
RepositoryFunction
bulunur. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için sabit kodlanmış bir haritadır.
Bir depo getirmek çok pahalı olabileceğinden, çeşitli önbelleğe alma katmanları vardır:
- İndirilen dosyalar için sağlama toplamı (
RepositoryCache
) ile girilen bir önbellek vardır. Bu, sağlama toplamının WORKSPACE dosyasında mevcut olmasını gerektirir, ancak yine de hermetik açıdan iyidir. Bu, çalıştırıldığı çalışma alanından veya çıkış tabanından bağımsız olarak aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır. $OUTPUT_BASE/external
altında her depo için, depoyu getirmek için kullanılan kuralın sağlama toplamını içeren bir "işaretçi dosyası" yazılır. Bazel sunucusu yeniden başlatılırsa ancak sağlama toplamı değişmezse, yeniden getirilmez. Bu,RepositoryDelegatorFunction.DigestWriter
üzerinde uygulanır .--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 ortamlarda yararlıdır. Bu,DownloadManager
tarafından uygulanır.
Bir depo indirildikten sonra, içindeki yapıların kaynak yapı olarak değerlendirilmesi gerekir. Bazel genellikle kaynak yapıların güncelliğini kontrol etmek için stat() işlevini çağırır. Bu yapıların bulunduğu deposun tanımı değiştiğinde de yapıların geçerliliği iptal edilir. Bu nedenle, bu durum bir sorun teşkil eder. Bu nedenle, harici 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, Workspace kök dizininin altındaki dosyaları (ör. indirilen paketleri kaynak ağacın bir alt dizininde barındıran bir paket yöneticisi) değiştirmesi gerekir. Bu, Bazel'in kaynak dosyaların yalnızca kullanıcı tarafından değiştirildiği ve kendisi tarafından değiştirilmediği varsayımı ile çelişir ve paketlerin, çalışma alanı kökünün altındaki her dizine atıfta bulunmasına olanak tanır. Bazel, bu tür bir dış depo çalışması yapabilmek için iki şey yapıyor:
- Kullanıcının, Bazel'in erişmesine izin verilmeyen Workspace alt dizinlerini belirtmesine olanak tanır. Bunlar,
.bazelignore
adlı bir dosyada listelenir ve işlev,BlacklistedPackagePrefixesFunction
ürününde uygulanır. - Workspace'in alt dizininden, yönetildiği harici depolamaya giden eşlemeyi
ManagedDirectoriesKnowledge
olarak kodlarız ve bunlara atıfta bulunanFileStateValue
'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 etiketlerinin yeniden eşlenmesine olanak tanır. Böylece @guava//
dizesi, bir ikili programın deposundaki bir Guava deposuna (@guava1//
gibi) ve diğerinin deposuna (@guava2//
gibi) başka bir Guava deposuna referans verebilir.
Alternatif olarak, elmasları birleştirmek için de kullanılabilir. Bir depo @guava1//
'e, diğer depo ise @guava2//
'a bağlıysa depo eşleme, standart bir @guava//
deposu kullanmak için her iki depoyu da yeniden eşlemenize 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ğerine sahip özellikleriniRuleClass.populateRuleAttributeValues()
tarafından dönüştürmek için kullanılır.- Analiz aşamasında kullanılan
Package.repositoryMapping
(yükleme aşamasında ayrıştırılmayan$(location)
gibi öğeleri çözmek için) - load() ifadelerindeki etiketleri çözümlemek için
BzlLoadFunction
JNI bitleri
Bazel sunucusu _çoğunlukla _Java ile yazılmıştır. Bunun istisnası, Java'nın kendi başına yapamadığı veya uygulama sırasında kendi başına yapamadığı kısımlarıdır. Bunun nedeni çoğunlukla dosya sistemi, işlem denetimi ve diğer alt düzey şeylerle olan etkileşimdir.
C++ kodu src/main/native altında bulunur ve yerel yöntemlere sahip Java sınıfları şunlardır:
NativePosixFiles
veNativePosixFileSystem
ProcessUtils
WindowsFileOperations
veWindowsFileProcesses
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. Daha sonra bu akışlar BlazeCommandDispatcher.execExclusively()
bölgesine 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.
Böylece hata ayıklama bilgileri System.err.println()
kullanılarak yazdırılabilir ve istemcinin terminal çıkışında (sunucudan farklıdır) bulunabilir. Bir işlem ikili çıktı (bazel query --output=proto
gibi) üretirse stdout'un kullanılamayacağına dikkat edilir.
Kısa mesajlar (hatalar, uyarılar ve benzeri) 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
'ün bir EventKind
(hata, uyarı, bilgi ve birkaçı daha) ve Location
(kaynak kodda etkinliğin gerçekleşmesine neden olan yer) olabilir.
Bazı EventHandler
uygulamaları, aldıkları etkinlikleri depolar. Bu, önbelleğe alınmış çeşitli işlemlerden (ör. önbelleğe alınmış yapılandırılmış bir hedef tarafından yayınlanan uyarılar) kaynaklanan bilgileri kullanıcı arayüzünde yeniden oynatmak için kullanılır.
Bazı EventHandler
'ler, sonunda etkinlik otobüsüne ulaşan etkinliklerin yayınlanmasına da izin verir (normal Event
'lar burada _not _görünmez). Bunlar ExtendedEventHandler
'ün uygulamalarıdır ve ana kullanım alanları, önbelleğe alınmış EventBus
etkinliklerini yeniden oynatmaktır. Bu EventBus
etkinliklerinin tümü Postable
'u uygular ancak EventBus
'te yayınlanan her şey bu arayüzü uygulamaz. Yalnızca bir ExtendedEventHandler
tarafından önbelleğe alınanlar bu arayüzü uygular (bunun yapılması iyi olur ve çoğu şey bunu yapar ancak zorunlu tutulmaz).
Terminal çıkışı çoğunlukla UiEventHandler
üzerinden yayınlanır. UiEventHandler
, Bazel'in yaptığı tüm süslü çıkış biçimlendirmesinden ve ilerleme raporlarından sorumludur. İki girişi vardır:
- Etkinlik otobüsü
- Reporter aracılığıyla aktarılan etkinlik akışı
Komut yürütme mekanizmasının (örneğin, Bazel'in geri kalanı) istemciye giden RPC akışıyla tek doğrudan bağlantısı, bu akışlara doğrudan erişime olanak tanıyan Reporter.getOutErr()
üzerindendir. Yalnızca bir komutun büyük miktarda olası ikili veriyi (bazel query
gibi) dökmesi gerektiğinde kullanılır.
Bazel'i profilleme
Bazel hızlı. Bazel de yavaştır çünkü derlemeler, dayanılabilirliğin sınırına kadar büyür. Bu nedenle Bazel'de, yapı profilleri ve Bazel'in kendisi için kullanılabilecek bir profil aracı bulunur. Profiler
adlı bir sınıfta uygulanır. Varsayılan olarak etkindir ancak ek yükünün tolere edilebilir olması için yalnızca kısaltılmış verileri kaydeder. Komut satırı--record_full_profiler_data
, mümkün olan her şeyi kaydetmesini sağlar.
Chrome profilleyici biçiminde bir profil oluşturur. Bu profili en iyi şekilde Chrome'da görüntüleyebilirsiniz. Veri modeli, görev yığınlarına benzer: Görevler başlatılabilir ve sonlandırılabilir ve bunların birbirine düzgün bir şekilde yerleştirilmesi gerekir. Her Java iş parçacığı kendi görev yığınını alır. TODO: Bu, işlemler ve devam etme aktarma stiliyle nasıl çalışır?
Profil oluşturucu, sırasıyla BlazeRuntime.initProfiler()
ve BlazeRuntime.afterCommand()
için başlatılır ve durdurulur ve her şeyin profilini çıkarabilmek için mümkün olduğunca uzun süre yayında kalmaya çalışır. Profile öğe eklemek için
Profiler.instance().profile()
numaralı telefonu arayın. Kapanışı 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. Öncekilerine "entegrasyon testleri" ve sonrakilere "birim testleri" adını veriyoruz. Ancak bu testler, daha az entegre edilmiş entegrasyon testlerine benzer. Ayrıca, gerekli olduğu bazı gerçek birim testlerimiz de vardır.
İki tür entegrasyon testi vardır:
src/test/shell
altında çok ayrıntılı bir bash test çerçevesi kullanılarak uygulananlar- Java'da uygulananlar. Bunlar
BuildIntegrationTestCase
sınıfının alt sınıfları olarak uygulanır.
BuildIntegrationTestCase
, çoğu test senaryosu için gerekli donanıma sahip olduğundan tercih edilen entegrasyon testi çerçevesidir. Java çerçevesi olduğundan hata ayıklama özelliğine sahiptir ve yaygın olarak kullanılan birçok geliştirme aracıyla sorunsuz entegrasyon sağlar. Bazel deposunda birçok BuildIntegrationTestCase
sınıfı örneği vardı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.