Bu belge, kod tabanının ve Bazel'ın yapısının bir açıklamasıdır. Bu özellik, son kullanıcılar için değil, Bazel'e katkıda bulunmak isteyen kişiler için tasarlanmıştır.
Giriş
Bazel'in kod tabanı büyüktür (yaklaşık 350KLOC üretim kodu ve yaklaşık 260 KLOC test kodu) ve bu manzaranın tamamına kimse aşina değildir: Herkes kendi vadisini çok iyi bilir, ama her yönde bulunan tepelerde neler olduğunu çok az kişi bilir.
Yolculuğun ortasında, yolun ortasında kaybolan bir ormanın karanlıkta kaybolmamaları için bu belge, kod tabanıyla ilgili 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.com/bazelbuild/bazel adresindeki GitHub'da yer alır. Bu, "bilgi kaynağı" değildir. Google dışında yararlı olmayan ek işlevler içeren Google dahili kaynak ağacından türetilir. Uzun vadeli hedef, GitHub'ı bilginin kaynağı yapmaktır.
Katkılar, normal GitHub pull istek mekanizmasıyla kabul edilir, bir Google çalışanı tarafından dahili kaynak ağacına manuel olarak içe aktarılır, ardından tekrar GitHub'a aktarılır.
İstemci/sunucu mimarisi
Bazel'in büyük bir kısmı, derlemeler arasında RAM'de kalan bir sunucu işleminde bulunur. Bu, Bazel'ın yapılar arasında durumu korumasına olanak tanır.
Bu nedenle Bazel komut satırında iki tür seçenek vardır: başlatma ve komut. Aşağıdaki gibi bir komut satırında:
bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar
Bazı seçenekler (--host_jvm_args=
), çalıştırılacak komutun adından önce, bazıları ise (-c opt
) sonrasında yer alır. İlk tür "başlangıç seçeneği" olarak adlandırılır ve sunucu işlemini bütünü olarak etkiler. İkinci seçenek ise "komut seçeneği" yalnızca tek bir komutu etkiler.
Her sunucu örneğinin tek bir ilişkilendirilmiş kaynak ağacı ("çalışma alanı") ve her çalışma alanının genellikle tek bir etkin sunucu örneği vardır. Bu, özel bir çıkış tabanı belirterek atlatılabilir (daha fazla bilgi için "Dizin düzeni" bölümüne bakın).
Bazel, aynı zamanda geçerli bir .zip dosyası olan tek bir ELF yürütülebilir dosyası olarak dağıtılır.
bazel
yazdığınızda, C++'ta uygulanan yukarıdaki ELF yürütülebilir dosyası ("istemci") kontrol edilir. Aşağıdaki adımları uygulayarak uygun bir sunucu işlemi oluşturur:
- Kendisini zaten ayıklamış olup olmadığını kontrol eder. Öyle değilse bu şekilde çalışır. Sunucu uygulaması da buradan gelir.
- Çalışan etkin bir sunucu örneği olup olmadığını kontrol eder: Örnek çalışıyor, doğru başlangıç seçeneklerine sahip ve doğru çalışma alanı dizinini kullanıyor. Çalışan sunucuyu, sunucunun dinlediği bağlantı noktasına sahip bir kilit dosyasının bulunduğu
$OUTPUT_BASE/server
dizinine bakarak bulur. - Gerekirse eski sunucu işlemini sonlandırır
- Gerekirse yeni bir sunucu işlemi başlatır
Uygun bir sunucu işlemi hazır olduğunda, çalıştırılması gereken komut ona bir gRPC arayüzü üzerinden iletilir, ardından Bazel çıkışı tekrar terminale aktarılır. Aynı anda yalnızca bir komut çalışabilir. Bu, C++ parçaları ve Java'daki bölümleriyle ayrıntılı bir kilitleme mekanizması kullanılarak uygulanır. bazel version
komutunu başka bir komutla paralel olarak çalıştıramamak utanç verici olduğundan birden fazla komutu paralel olarak çalıştırmak için bazı altyapılar mevcuttur. Ana engelleyici, BlazeModule
saniyelerinin ve BlazeRuntime
bölgesindeki
bazı durumların yaşam döngüsüdür.
Bir komutun sonunda, Bazel sunucusu istemcinin döndürmesi gereken çıkış kodunu iletir. İlginç bir kısım da bazel run
uygulanmasıdır: Bu komutun işi Bazel'ın yeni oluşturduğu bir şeyi çalıştırmaktır, ancak terminal olmadığı için bunu sunucu işleminden yapamaz. Bunun yerine, istemciye hangi ikili programı ujexec()
ve hangi bağımsız değişkenleriyle kullanması gerektiğini söyler.
Ctrl-C tuşlarına basıldığında istemci, bunu gRPC bağlantısındaki bir İptal çağrısına çevirir ve bu çağrı, komutu mümkün olan en kısa sürede sonlandırmaya çalışır. Üçüncü Ctrl-C'den sonra istemci, bunun yerine sunucuya bir SIGKILL gönderir.
İstemcinin kaynak kodu src/main/cpp
altında ve sunucuyla iletişim kurmak için kullanılan protokol src/main/protobuf/command_server.proto
dilindedir .
Sunucunun ana giriş noktası BlazeRuntime.main()
'dir 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 dizin kümesi oluşturuyor. Tam açıklama Çıkış dizini düzeninde mevcuttur.
"Çalışma alanı", Bazel'ın çalıştırıldığı kaynak ağacıdır. Bu genellikle kaynak kontrolünden baktığınız bir şeye karşılık gelir.
Bazel, tüm verilerini "çıkış kullanıcı kökü" altına yerleştirir. Bu genellikle $HOME/.cache/bazel/_bazel_${USER}
olur ancak --output_user_root
başlatma seçeneği kullanılarak geçersiz kılınabilir.
"Yükleme tabanı", Bazel'ın ayıklandığı yerdir. Bu işlem otomatik olarak yapılır ve her Bazel sürümü, yükleme tabanının altındaki sağlamasına göre bir alt dizin alır. Varsayılan olarak $OUTPUT_USER_ROOT/install
şeklindedir 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 yazma işlemi yaptığı yerdir. Her çıkış tabanında, aynı anda çalışan en fazla bir Bazel sunucu örneği bulunur. Genelde 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, herhangi bir çalışma alanında aynı anda yalnızca bir Bazel örneğinin çalışabileceği sınırlamasını atlatmak için yararlıdır.
Çıkış dizini, diğerlerinin yanı sıra şunları içerir:
$OUTPUT_BASE/external
konumundaki harici depolar getirildi.- Geçerli derlemenin tüm kaynak koduna sembolik bağlantıları içeren bir dizin olan exec kök dizini. Adresi:
$OUTPUT_BASE/execroot
. Derleme sırasında çalışma dizini$EXECROOT/<name of main repository>
'dir. Çok uyumsuz bir değişiklik olduğundan uzun vadeli bir plan olsa da bunu$EXECROOT
olarak değiştirmeyi planlıyoruz. - Derleme sırasında oluşturulan dosyalar.
Bir komut yürütme süreci
Bazel sunucusu kontrolü aldıktan ve yürütmesi gereken bir komut hakkında bilgilendirildikten sonra aşağıdaki olaylar dizisi gerçekleşir:
BlazeCommandDispatcher
yeni istek hakkında bilgilendirildi. Komutun içinde çalışacak bir çalışma alanı gerekip gerekmediğini (sürüm veya yardım gibi kaynak kodla ilgisi olmayan komutlar dışında hemen hemen her komut) ve başka bir komutun çalışıp çalışmayacağını belirler.Doğru komut bulundu. Her komut,
BlazeCommand
arayüzünü uygulamalı ve@Command
ek açıklamasına sahip olmalıdır (bu biraz antipattern'dir. Bir komutun ihtiyaç duyduğu tüm meta verilerBlazeCommand
üzerinde yöntemler tarafından açıklansa güzel olurdu)Komut satırı seçenekleri ayrıştırılır. Her komutun,
@Command
ek açıklamasında açıklanan farklı komut satırı seçenekleri vardı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 duyurmak için Derleme Etkinlik Protokolü'nün gözetimi kapsamında Bazel'in dışına aktarılıyor.
Kontrolü komut alır. En ilginç komutlar bir derleme çalıştıran komutlardır: derleme, test, çalıştırma, kapsam vb.: Bu işlev
BuildTool
tarafından uygulanır.Komut satırındaki hedef kalıpları grubu ayrıştırılır ve
//pkg:all
,//pkg/...
gibi joker karakterler çözümlenir. Bu,AnalysisPhaseRunner.evaluateTargetPatterns()
uygulamasında uygulanır ve Skyframe'deTargetPatternPhaseValue
olarak yeniden ayarlanır.Yükleme/analiz aşaması, işlem grafiğini (derleme için yürütülmesi gereken komutların yönlendirilmiş bir düzensiz grafiği) üretmek için ç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 yürütülmesi anlamına gelir.
Komut satırı seçenekleri
Bazel çağrısı için komut satırı seçenekleri bir OptionsParsingResult
nesnesinde açıklanmıştır. Bu nesne, "seçenek sınıfları"ndan seçeneklerin değerlerine kadar uzanan bir harita içerir. "Seçenek sınıfı", OptionsBase
alt sınıfıdır ve birbirleriyle ilişkili komut satırı seçeneklerini birlikte gruplandırır. Örneğin:
- Bir programlama diliyle (
CppOptions
veyaJavaOptions
) ilgili seçenekler. BunlarFragmentOptions
alt sınıfı olmalıdır ve sonunda birBuildOptions
nesnesine sarmalanır. - Bazel'ın işlemleri yürütme şekliyle ilgili seçenekler (
ExecutionOptions
)
Bu seçenekler, analiz aşamasında ve (Java'da RuleContext.getFragment()
veya Starlark'ta ctx.fragments
aracılığıyla) kullanılacak şekilde tasarlanmıştır.
Bunlardan bazıları (örneğin, C++'nın tarama dahil edilip edilmeyeceğini) yürütme aşamasında okunur, ancak BuildConfiguration
o zaman kullanılamadığından her zaman açıkça tesisat kurulumu gerekir. Daha fazla bilgi için "Yapılandırmalar" bölümüne bakın.
UYARI: OptionsBase
örneklerinin sabit olduğunu ve bu şekilde (SkyKeys
öğesinin bir kısmı gibi) kullanıldığını varsaymak isteriz. Böyle bir durum söz konusu değildir ve bunları değiştirmek, Bazel'ı hata ayıklaması zor, kolayca kırmak için gerçekten iyi bir yöntemdir. Ne yazık ki, onları gerçekten sabit bir hale getirmek büyük bir girişim.
(Bir FragmentOptions
, yapım işleminden hemen sonra ve başka birisi buna referansta bulunma fırsatı bulmadan ve equals()
veya hashCode()
çağrılmadan önce üzerinde değişiklik yapılması herhangi bir soruna yol açmaz.)
Bazel, seçenek sınıflarını aşağıdaki şekillerde öğrenir:
- Bazıları Bazel'e (
CommonCommandOptions
) bağlı olarak kullanılıyor - Her Bazel komutundaki
@Command
ek açıklamadan ConfiguredRuleClassProvider
kaynağından (bunlar tek programlama dilleriyle ilgili komut satırı seçenekleridir)- Starlark kuralları kendi seçeneklerini de tanımlayabilir (buraya göz atı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 ek açıklama, bir yardım metniyle birlikte komut satırı seçeneğinin adını ve türünü belirtir.
Bir komut satırı seçeneğinin değerinin Java türü genellikle basit bir değerdir (dize, tam sayı, Boole, etiket vb.). Bununla birlikte, daha karmaşık tür seçenekleri de desteklenmektedir. Bu durumda, komut satırı dizesinden veri türüne dönüştürme işi, com.google.devtools.common.options.Converter
kapsamına girer.
Bazel'in gördüğü kaynak ağaç
Bazel, kaynak kodunu okuyup yorumlayarak gerçekleşen yazılım geliştirme işindedir. Bazel'ın üzerinde çalıştığı kaynak kodunun tamamı "çalışma alanı" olarak adlandırılır ve depolar, paketler ve kurallar içinde yapılandırılır.
Depolar
"Depo", bir geliştiricinin üzerinde çalıştığı bir kaynak ağacıdır. Genellikle tek bir projeyi temsil eder. Bazel'ın atası Blaze, bir monorepo üzerinde, yani derlemeyi çalıştırmak için kullanılan tüm kaynak kodlarını içeren tek bir kaynak ağacı üzerinde çalışıyordu. Bazel ise kaynak kodu birden fazla depoya yayılan projeleri destekler. Bazel'ın çağrıldığı depo "ana depo", diğerleri ise "harici depolar" olarak adlandırılır.
Depo, kök dizininde WORKSPACE
(veya WORKSPACE.bazel
) adlı bir dosyayla işaretlenir. Bu dosya, tüm derleme için "genel" bilgiler (ör. mevcut harici depolar grubu) içerir. Normal bir Starlark dosyası gibi çalışır; bu da diğer Starlark dosyalarını load()
yapabileceğiniz anlamına gelir.
Bu, genellikle açıkça referans verilen bir deponun ihtiyaç duyduğu depoları çekmek için kullanılır (buna "deps.bzl
kalıbı" adını veririz)
Harici depoların kodu, $OUTPUT_BASE/external
altında sembollerle bağlanır veya indirilir.
Derlemeyi çalıştırırken kaynak ağacının tamamının birleştirilmesi gerekir. Bu işlem SymlinkForest
tarafından yapılır. Bu işlem, ana depodaki her paketi $EXECROOT
'a ve her harici kod deposunu $EXECROOT/external
veya $EXECROOT/..
ile sembollere bağlar (ilk veri havuzu, ana depoda external
adlı bir paketin bulunmasını elbette imkansız hale getirir. Bu nedenle, ana depodan geçiş yapıyoruz).
Paketler
Her depo; paketlerden, ilgili dosyaların bir koleksiyonundan ve bağımlılıkların bir spesifikasyonundan oluşur. Bunlar, BUILD
veya BUILD.bazel
adlı bir dosyayla belirtilir. Her ikisi de varsa Bazel BUILD.bazel
seçeneğini tercih eder. BUILD
dosyalarının hâlâ kabul edilmesinin nedeni, Bazel'ın üst öğesi olan Blaze'in bu dosya adını kullanmasıdır. Ancak, özellikle dosya adlarının büyük/küçük harfe duyarlı olmadığı Windows'da yaygın olarak kullanılan bir yol segmenti olduğu ortaya çıktı.
Paketler birbirinden bağımsızdır: 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ığı yinelenmeyi durduracağından BUILD
dosyalarının eklenmesi veya kaldırılması diğer paketleri _değiştirebilir.
BUILD
dosyalarının değerlendirilmesine "paket yükleniyor" denir. PackageFactory
sınıfında uygulanır, Starlark çevirmenini çağırarak çalışır ve mevcut kural sınıfları kümesi hakkında bilgi sahibi olmayı gerektirir. Paket yükleme işleminin sonucunda Package
nesnesi oluşur. Çoğunlukla bir dizeden (hedefin adı) hedefe uzanan bir haritadır.
Paket yükleme sırasında karmaşıklığın büyük bir kısmı globalleştirmedir: Bazel, her kaynak dosyanın açıkça listelenmesini gerektirmez ve bunun yerine glob'ları (glob(["**/*.java"])
gibi) çalıştırabilir. Kabuktan farklı olarak, alt dizinlere inen (ancak alt paketlere inmeyen) yinelemeli glob'ları destekler. Bu işlem, dosya sistemine erişim gerektirir ve bu yavaş olabileceği için sistemin paralel ve verimli bir şekilde çalışmasını sağlamak için her türlü hileyi uygularız.
Globbing aşağıdaki sınıflarda uygulanır:
LegacyGlobber
, Skyframe'in farkında olmayan hızlı ve keyifli bir küreSkyframeHybridGlobber
, Skyframe'i kullanan ve "Skyframe yeniden başlatılmalarını" önlemek için eski globber'a geri dönen bir sürüm (aşağıda açıklanmıştır)
Package
sınıfının kendisi, özel olarak WORKSPACE dosyasını ayrıştırmak için kullanılan ve gerçek paketler için bir anlam ifade etmeyen bazı üyeler içerir. Bu bir tasarım hatasıdır çünkü normal paketleri açıklayan nesnelerin başka bir şeyi tanımlayan alanlar içermemesi gerekir. Bunlardan bazıları:
- Depo eşlemeleri
- Tescilli araç zincirleri
- Kayıtlı yürütme platformları
İdeal olarak, WORKSPACE dosyası normal paketlerin ayrıştırılmasından daha fazla ayrılmalıdır. Böylece Package
, her iki paketin de ihtiyaçlarını karşılamak zorunda kalmaz. Ne yazık ki bunu yapmak, oldukça zordur
çünkü ikisi birbiriyle iç içe geçmiştir.
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 eserler (başka yerde de anlatılmıştır) diyoruz. Derleme sırasında oluşturulan tüm dosyalar hedef değildir; Bazel çıktısının ilişkili bir etikete sahip olmaması yaygın görülen bir durumdur.
- Kurallar: Bunlar, çıktılarını girişlerden elde etmeye yönelik adımları açıklar. Bunlar genellikle bir programlama diliyle (
cc_library
,java_library
veyapy_library
gibi) ilişkilendirilir ancak dilden bağımsız olanlar da vardır (ör.genrule
veyafilegroup
) - Paket grupları: Görünürlük bölümünde ele alınır.
Hedefin adına Etiket adı verilir. Etiketlerin söz dizimi @repo//pac/kage:name
şeklindedir. Burada repo
Etiketin bulunduğu deponun adı; pac/kage
, BUILD
dosyasının bulunduğu dizindir ve name
ise paketin dizinine göre dosyanın yoludur (etiket bir kaynak dosyaya başvuruda bulunuyorsa). Komut satırında bir hedefe atıfta bulunurken etiketin bazı kısımları atlanabilir:
- Depo atlanırsa etiketin ana depoda olduğu kabul edilir.
- Paket bölümü atlanırsa (
name
veya:name
gibi) etiket, geçerli çalışma dizininin paketinde olduğu kabul edilir (üst düzey referanslar içeren göreli yollara (..) izin verilmez)
Bir kural türüne (ör. "C++ kitaplığı") "kural sınıfı" adı verilir. Kural sınıfları, Starlark'ta (rule()
işlevi) veya Java'da (yani "yerel kurallar", RuleClass
yazın) uygulanabilir. Uzun vadede, dile özgü her kural Starlark'ta uygulanacaktır, ancak bazı eski kural aileleri (Java veya C++ gibi) şimdilik Java'dadır.
Starlark kural sınıflarının, BUILD
dosyalarının başında load()
ifadesi kullanılarak içe aktarılması gerekir. Java kural sınıfları, ConfiguredRuleClassProvider
ile kaydettirildiği için Bazel tarafından "doğuştan" olarak bilinir.
Kural sınıfları aşağıdakilere benzer bilgiler içerir:
- Özellikleri (ör.
srcs
,deps
): türleri, varsayılan değerleri, kısıtlamalar vb. - Varsa her özelliğe eklenmiş yapılandırma geçişleri ve yönler
- Kuralın uygulanması
- "Genellikle" kuralının oluşturduğu geçişli bilgi sağlayıcılar
Terminoloji notu: Kod tabanında, genellikle bir kural sınıfı tarafından oluşturulan hedefi belirtmek için "Kural" ifadesini kullanırız. Ancak Starlark'ta ve kullanıcılara yönelik belgelerde "Rule" yalnızca kural sınıfını belirtmek için kullanılmalıdır. Hedef, yalnızca bir "hedef"tir. Ayrıca RuleClass
adında "class" bulunmasına rağmen, bir kural sınıfı ile bu türdeki hedefler arasında Java devralma ilişkisi olmadığını unutmayın.
Gökyüzü Çerçevesi
Bazel'in temelini oluşturan değerlendirme çerçevesine Skyframe denir. Bu modelin modeli, bir derleme sırasında derlenmesi gereken her şeyin, kenarları herhangi bir veri parçasından bağımlılıklarına, yani onu oluşturmak için bilinmesi gereken diğer veri parçalarına işaret eden yönlendirilmiş eşzamansız bir grafik halinde düzenlenmesidir.
Grafikteki düğümler SkyValue
, adları ise SkyKey
olarak adlandırılır. Her ikisi de son derece sabittir; bunlardan yalnızca sabit nesnelere ulaşılabilir. Bu değişmeyen neredeyse her zaman geçerlidir. Olması halinde (BuildConfigurationValue
ve SkyKey
öğelerinin üyesi olan BuildOptions
bağımsız seçenek
sınıflarında olduğu gibi) bunları değiştirmemek
veya yalnızca dışarıdan gözlemlenemeyen şekillerde değiştirmek için gayretle çalışıyoruz.
Buradan yola çıkarak, Skyframe'de hesaplanan her şeyin (yapılandırılmış hedefler gibi) sabit olması gerekir.
Skyframe grafiğini gözlemlemenin en uygun yolu, grafiği her satıra bir SkyValue
gelecek şekilde dağıtan bazel dump
--skyframe=deps
çalıştırmaktır. Bu işlemi küçük yapılarda yapmak
en iyisidir, çünkü oldukça büyük olabilir.
Skyframe, com.google.devtools.build.skyframe
paketinde yer alır. Benzer bir şekilde adlandırılmış com.google.devtools.build.lib.skyframe
paket, Skyframe'in üzerine Bazel uygulamasını içerir. Skyframe hakkında daha fazla bilgiyi burada bulabilirsiniz.
Belirli bir SkyKey
öğesini SkyValue
olarak değerlendirmek için Skyframe, anahtarın türüne karşılık gelen SkyFunction
işlevini çağırır. İşlevin değerlendirmesi sırasında, çeşitli SkyFunction.Environment.getValue()
aşırı yüklemelerini çağırarak Skyframe'den başka bağımlılıklar isteyebilir. Bu, söz konusu bağımlılıkları Skyframe'in dahili grafiğine kaydetmek gibi bir yan etkiye sahiptir. Böylece Skyframe, bağımlılıklarından herhangi biri değiştiğinde işlevi yeniden değerlendireceğini bilir. Başka bir deyişle, Skyframe'in önbelleğe alma ve artımlı hesaplaması SkyFunction
sn. ve SkyValue
sn. ayrıntı düzeyinde çalışır.
Bir SkyFunction
, kullanılamayan bir bağımlılık istediğinde getValue()
boş sonucunu döndürür. Daha sonra işlev, kendi başına boş değer döndürerek Skyframe'e geri kontrol sağlamalıdır. Daha sonraki bir noktada Skyframe, kullanılamayan bağımlılığı değerlendirir ve işlevi baştan başlatır. Ancak bu sefer getValue()
çağrısı null olmayan bir sonuçla başarılı olur.
Bu nedenle, yeniden başlatmadan önce SkyFunction
içinde gerçekleştirilen 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, bu sorunu çözmek için genellikle
şu şekilde çalışıyoruz:
- Yeniden başlatma sayısını sınırlamak için bağımlılıkları gruplar halinde bildirme (
getValuesAndExceptions()
kullanarak). - Bir
SkyValue
öğesini farklıSkyFunction
'lar tarafından hesaplanan ayrı parçalara bölerek bağımsız olarak hesaplanabilir ve önbelleğe alınabilir. Bellek kullanımını artırma potansiyeline sahip olduğundan bu işlem stratejik olarak yapılmalıdır. SkyFunction.Environment.getState()
kullanarak veya geçici bir statik önbelleği "Skyframe'in arkasında" tutarak yeniden başlatmalar arasında durum depolama.
Esasen, bu tür geçici çözümlere ihtiyacımız var. Çünkü düzenli olarak yayındaki yüz binlerce 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ü dildir. Python'un çok daha az türe, kontrol akışı üzerinde daha fazla kısıtlamaya ve en önemlisi, eşzamanlı okumalara olanak tanıyan güçlü sabitlik garantilerine sahip kısıtlanmış bir alt kümesi olarak düşünülür. Bu, tüm kullanıcıları değil, bazılarını dil içinde genel programlama görevlerini tamamlamaya çalışmaktan vazgeçiren Turing eksiksiz bir yaklaşım değildir.
Starlark, net.starlark.java
paketinde uygulandı.
Ayrıca, buradan bağımsız bir Go uygulaması vardır. Bazel'de kullanılan Java uygulaması şu anda çevirmendir.
Starlark, aşağıdakileri de içeren ç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 içeriğine ve bu dosya tarafından yüklenen.bzl
dosyaya erişebilir.- Kural tanımları. Yeni kurallar (yeni dil desteği gibi) bu şekilde tanımlanır. Bu bağlamda çalışan Starlark kodu, doğrudan bağımlılıklarının sağladığı yapılandırmaya ve verilere erişebilir (ileride daha fazla bilgi edinecektir).
- WORKSPACE dosyası. Harici depolar (ana kaynak ağacında yer almayan kod) tanımlanır.
- Kod deposu kuralı tanımları. Yeni harici depo türleri burada tanımlanır. Bu bağlamda çalışan Starlark kodu, Bazel'ın çalıştığı makinede rastgele kod çalıştırabilir ve çalışma alanının dışına erişebilir.
BUILD
ve .bzl
dosyalarının lehçeleri farklı şeyleri ifade ettiklerinden
biraz farklıdır. Farklılıkların listesini burada bulabilirsiniz.
Buradan Starlark hakkında daha fazla bilgi edinebilirsiniz.
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, aslında bir (hedef, yapılandırma) çifti olan "yapılandırılmış hedef"tir.
Bu aşama, daha önce serileştirilerek şimdi zamanla üst üste gelen iki ayrı bölüme ayrılabildiği için "yükleme/analiz aşaması" olarak adlandırılır:
- Paketler yükleniyor, yani
BUILD
dosyalarını onları temsil edenPackage
nesnelerine dönüştürme - Yapılandırılmış hedefleri analiz etme, yani eylem grafiğini oluşturmak için kuralların uygulanmasını uygulama.
Komut satırında istenen yapılandırılmış hedeflerin geçişli kapatılmasındaki her yapılandırılmış hedef, aşağıdan yukarıya analiz edilmelidir. Yani önce yaprak düğümleri, sonra komut satırındakiler analiz edilmelidir. Tek bir yapılandırılmış hedefin analizinde kullanılan girişler şunlardır:
- Yapılandırma. ("bu kuralın nasıl oluşturulacağı"; örneğin, hedef platform ve kullanıcının C++ derleyicisine iletilmesini istediği komut satırı seçenekleri gibi)
- Doğrudan bağımlılıklar. Analiz edilen kural, geçişli bilgi sağlayıcılarını kullanabilir. Sınıf yolundaki tüm .jar dosyaları veya bir C++ ikili programına bağlanması gereken tüm .o dosyaları gibi yapılandırılmış hedefin geçişli kapanışında bilgilerin "toplayıcısını" sağladıkları için bu şekilde adlandırılmıştır.)
- Hedefin kendisi. Bu, hedefin bulunduğu paketin yüklenmesinden kaynaklanır. Kurallar açısından, bu, genellikle önemli olan özellikleri de içerir.
- Yapılandırılmış hedefin uygulanması. Kurallar için bu, Starlark'ta veya Java'da olabilir. Kural olmayan tüm hedefler Java'da uygulanır.
Yapılandırılmış bir hedefin analiz edilmesinin sonucu:
- Buna bağlı hedefleri yapılandıran geçişli bilgi sağlayıcılar
- Oluşturabileceği eserler ve bunları üreten eylemler.
Java kurallarına sunulan RuleContext
API'dir. Bu API, Starlark kurallarının ctx
bağımsız değişkenine eşdeğerdir. API'si daha güçlüdür ancak aynı zamanda Bad ThingsTM'i yapmak (örneğin zaman veya alan karmaşıklığı ikinci dereceden (veya daha kötü) olan kod yazmak), Java istisnasıyla Bazel sunucusunun kilitlenmesine neden olmak veya sabit değerleri ihlal etmek (ör. bir Options
örneğini yanlışlıkla değiştirerek ya da yapılandırılmış bir hedefi değişebilir hale getirerek) Bad ThingsTM'i yapmak daha kolaydır.
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, hedef oluşturma işleminin "nasıl" yapıldığıdır: hangi platform için, hangi komut satırı seçenekleriyle vb.
Aynı derlemedeki birden fazla yapılandırma için aynı hedef oluşturulabilir. Ö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 yaparken veya yağsız bir Android uygulaması (birden fazla CPU mimarisi için yerel kod içeren) oluştururken bu yöntem yararlıdır.
Kavramsal olarak yapılandırma bir BuildOptions
örneğidir. Ancak pratikte BuildOptions
, ek işlevler sağlayan BuildConfiguration
tarafından sarmalanır. Bağımlılık grafiğinin üst kısmından
alta doğru yayılır. Derleme değişirse derlemenin
yeniden analiz edilmesi gerekir.
Bu durum, örneğin yalnızca test hedeflerini etkilemesine rağmen istenen test çalıştırmalarının sayısı değişirse derlemenin tamamını yeniden analiz etmek zorunda kalma gibi anormalliklere neden olur (Yapılandırmaları "kırpmayı" planlıyoruz, böyle bir durum söz konusu değildir ancak henüz hazır değildir).
Bir kural uygulaması, yapılandırmanın bir kısmına ihtiyaç duyduğunda, yapılandırmanın RuleClass.Builder.requiresConfigurationFragments()
kullanarak tanımında bunu açıklaması gerekir. Bu, hatalardan (Java parçasını kullanan Python kuralları gibi) önlemek ve Python seçenekleri değişirse C++ hedeflerinin yeniden analiz edilmesine gerek kalmayacak şekilde yapılandırmayı kırpmayı kolaylaştırmak için yapılır.
Bir kuralın yapılandırması, "üst" kuralının yapılandırmasıyla aynı olmayabilir. Bağımlılık ucundaki 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 ucunda. Bu geçişler
Attribute.Builder.cfg()
içinde belirtilmiştir veRule
(geçişin gerçekleştiği yer) veBuildOptions
(orijinal yapılandırma) ile bir veya daha fazlaBuildOptions
(çıkış yapılandırması) arasındaki işlevlerdir. - Yapılandırılmış bir hedefe gelen herhangi bir uçta. Bunlar
RuleClass.Builder.cfg()
içinde belirtilmiştir.
İlgili sınıflar: TransitionFactory
ve ConfigurationTransition
.
Yapılandırma geçişleri kullanılır, örneğin:
- Derleme sırasında belirli bir bağımlılığın kullanıldığını ve bu nedenle yürütme mimarisinde derlenmesi gerektiğini beyan etmek
- Belirli bir bağımlılığın birden fazla mimari için derlenmesi gerektiğini beyan etmek (örneğin, yağ Android APK'larındaki yerel kod için)
Bir yapılandırma geçişi birden fazla yapılandırmayla sonuçlanırsa bu geçişe bölünmüş geçiş denir.
Yapılandırma geçişleri Starlark'ta da uygulanabilir (belgeleri burada bulabilirsiniz)
Geçişli bilgi sağlayıcılar
Geçişli bilgi sağlayıcıları, yapılandırılmış hedeflerin buna bağımlı olan diğer yapılandırılmış hedefler hakkında bilgi vermelerini sağlayan bir yöntemdir (ve _only _way). Adlarında "geçişli" ifadesinin olmasının nedeni, bunun genellikle yapılandırılmış bir hedefin geçişli kapanışının bir şekilde toplanmasıdır.
Java geçişli bilgi sağlayıcıları ile Starlark sağlayıcıları arasında genellikle bire bir eşleşme vardır (FileProvider
, FilesToRunProvider
ve RunfilesProvider
ifadelerinin birleşimi olan DefaultInfo
, bu API'nin Java'nın doğrudan harf çevirisinden daha Starlark benzeri bir sıralamaya sahip olduğu düşünülür).
Anahtarı aşağıdakilerden biridir:
- Java Sınıfı nesnesi. Bu özellik yalnızca Starlark'tan erişilemeyen sağlayıcılar tarafından kullanılabilir. Bu sağlayıcılar
TransitiveInfoProvider
alt sınıfıdır. - Bir dize. Bu eski bir olaydır ve isim çakışmaları olabileceğinden kesinlikle önerilmez. Bu tür geçişli bilgi sağlayıcıları,
build.lib.packages.Info
adlı grubun 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ı oluşturmak için önerilen yöntemdir. Sembol, Java'da birProvider.Key
örneğiyle temsil edilir.
Java'da uygulanan yeni sağlayıcılar BuiltinProvider
kullanılarak uygulanmalıdır.
NativeProvider
kullanımdan kaldırıldı (henüz kaldırmak için vaktimiz olmadı) ve
TransitiveInfoProvider
alt sınıfa Starlark'tan erişilemiyor.
Yapılandırılmış hedefler
Yapılandırılmış hedefler RuleConfiguredTargetFactory
olarak uygulanır. Java'da uygulanan her kural sınıfı için bir alt sınıf vardır. Starlark'ın yapılandırılmış hedefleri 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. Şunları içerir:
filesToBuild
, "bu kuralın temsil ettiği dosya kümesi" gibi belirsiz bir kavramdır. Bunlar, yapılandırılmış hedef komut satırında veya bir türün src'lerinde olduğunda oluşturulan dosyalardır.- Çalışma dosyaları, normal veriler ve veriler.
- Kullanıcının çıkış grupları. Bunlar, kuralın oluşturabileceği çeşitli "diğer dosya gruplarıdır". Bunlara, BUILD'daki filegroup kuralının çıkış_grubu özelliği ve Java'da
OutputGroupInfo
sağlayıcısı kullanılarak erişilebilir.
Çalıştırma dosyaları
Bazı ikili programların çalışması için veri dosyaları gerekir. Belirgin bir örnek, giriş dosyaları gereken testlerdir. Bu, Bazel'de "runfiles" kavramıyla temsil edilir. "Çalıştırma dosyaları ağacı", belirli bir ikili program için veri dosyalarının dizin ağacıdır. Dosya sisteminde, çıkış ağaçlarının kaynağındaki dosyaları işaret eden bağımsız sembolik bağlantıların bulunduğu bir sembolik bağlantı ağacı olarak oluşturulur.
Bir çalıştırma dosyası grubu, Runfiles
örneği olarak temsil edilir. Kavram olarak, runfiles ağacındaki bir dosyanın yolundan dosyayı temsil eden Artifact
örneğine kadar olan bir haritadır. Bu, iki nedenden dolayı tek bir Map
özelliğinden biraz daha karmaşıktır:
- Çoğu zaman bir dosyanın runfiles yolu execpath ile aynıdır. Bu işlem RAM'den tasarruf etmek için kullanılır.
- Runfile ağaçlarında, temsil edilmesi gereken çeşitli eski tür girişler bulunur.
Çalışma dosyaları RunfilesProvider
kullanılarak toplanır: Bu sınıfın bir örneği, runfiles yapılandırılmış bir hedefi (kitaplık gibi) ve geçişli kapatma ihtiyaçlarını temsil eder ve iç içe yerleştirilmiş bir küme gibi toplanır (aslında, kapak altında iç içe yerleştirilmiş kümeler kullanılarak uygulanır): Her hedef birleşimleri, bağımlılıklarına ait çalıştırma dosyalarının bir kısmını ekler, ardından elde edilen kurulumu bağımlılık grafiğinde yukarıya gönderir. RunfilesProvider
örneği, biri kuralın "data" özelliği üzerinden bağımlı olduğu durumlarda ve diğer her tür gelen bağımlılık türü 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 normalde farklı olan farklı çalıştırma dosyaları sunmasıdır. Bu, henüz kaldırmadığımız eski, istenmeyen bir davranıştır.
İkili programların çalıştırma dosyaları, RunfilesSupport
örneği olarak temsil edilir. Bu, Runfiles
işleminden farklıdır, çünkü RunfilesSupport
gerçekte oluşturulma yeteneğine sahiptir (yalnızca bir haritalama olan Runfiles
'in aksine). Bu, aşağıdaki ek bileşenleri gerektirir:
- Giriş runfiles manifesti. Bu, çalıştırma dosyaları ağacının serileştirilmiş bir açıklamasıdır. Runfiles ağacının içerikleri 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şeceğini varsayar.
- Çıkış runfiles manifesti. Bu, bazen sembolik bağlantıları desteklemeyen Windows'da olmak üzere Runfile ağaçlarını işleyen çalışma zamanı kitaplıkları tarafından kullanılır.
- Runfiles aracısı. Bir runfiles ağacının var olması için, sembolik bağlantı ağacı ve sembollerin işaret ettiği yapı derlenmelidir. Bağımlılık kenarlarının sayısını azaltmak için tüm bunları temsil etmek üzere runfiles aracı kullanılabilir.
- Çalışma dosyalarında
RunfilesSupport
nesnesinin temsil ettiği ikili programın çalıştırılması için komut satırı bağımsız değişkenleri.
Bakış Açıları
Yönler, "işlemi bağımlılık grafiğine yaymanın" bir yoludur. Bunlar Bazel kullanıcıları için burada açıklanmıştır. İyi bir motivasyon örneği, protokol arabellekleridir: proto_library
kuralı belirli bir dili bilmemelidir. Ancak herhangi bir programlama dilinde protokol arabelleği mesajının ("protokol arabelleklerinin "temel birimi") uygulanması, proto_library
kuralına bağlanmalıdır. Böylece, aynı dildeki iki hedef aynı protokol arabelleğine bağlı olduğunda yalnızca bir kez derlenir.
Yapılandırılmış hedeflerde olduğu gibi, Skyframe'de de SkyValue
olarak temsil edilirler ve yapılandırılma şekilleri, yapılandırılan hedeflerin oluşturulma biçimine çok benzer: RuleContext
'ye erişimi olan ConfiguredAspectFactory
adlı bir fabrika sınıfı vardır ancak yapılandırılmış hedef fabrikaların aksine, bağlı olduğu yapılandırılmış hedefi ve sağlayıcılarını da bilir.
Bağımlılık grafiğinde aşağı yayılan en boy grubu, Attribute.Builder.aspects()
işlevi kullanılarak her özellik için belirtilir. Sürece dahil olan, farklı adlara sahip birkaç sınıf vardır:
AspectClass
, özelliğin uygulanmasıdır. Java'da (bu durumda bir alt sınıf) veya Starlark'ta (bu durumdaStarlarkAspectClass
örneğidir) olabilir.RuleConfiguredTargetFactory
ile benzerdir.AspectDefinition
, özelliğin tanımıdır. Gerekli sağlayıcıları, sağladığı sağlayıcıları ve ilgiliAspectClass
örneği gibi uygulanmasına ilişkin bir referans içerir.RuleClass
ile benzerdir.AspectParameters
, bağımlılık grafiğinde aşağı doğru yayılan bir yönü parametrize etmenin bir yoludur. Şu anda dize eşlemesi. Neden faydalı 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 dağıtılmalıdır.Aspect
, bağımlılık grafiğinde yayılım gösteren 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 yayılması gerektiğini belirleyen işlevdir. Bu birRule
->Aspect
işlevidir.
Beklenmedik bir sorun da bazı yönlerin diğer yönlere eklenebilmesidir. Örneğin, bir Java IDE için sınıf yolunu toplayan bir özellik muhtemelen sınıf yolundaki tüm .jar dosyaları hakkında bilgi almak isteyecektir. Ancak bunların bazıları protokol arabelleğidir. Bu durumda, IDE özelliği (proto_library
kuralı + Java proto en boy oranı) çiftine eklemek isteyecektir.
Yönlerin karmaşıklığını AspectCollection
dersinde öğrenebilirsiniz.
Platformlar ve araç zincirleri
Bazel, çok platformlu derlemeleri, yani derleme işlemlerinin çalıştığı birden fazla mimarinin ve kodun derlendiği birden fazla mimarinin bulunabileceği derlemeleri destekler. Bu mimariler, Bazel dilinde platformlar olarak adlandırılır (Tüm belgeleri burada bulabilirsiniz)
Platform, kısıtlı ayarlardan ("CPU mimarisi" kavramı gibi) kısıtlı değerlere (ör. x86_64 gibi belirli bir CPU) kadar bir anahtar/değer eşlemesi tarafından tanımlanır. @platforms
deposunda en sık kullanılan kısıtlama ayarlarının ve değerlerinin bir "sözlüğü" vardır.
Araç zinciri kavramı, derlemenin hangi platformlarda çalıştığına ve hangi platformların hedeflendiğine bağlı olarak farklı derleyicilerin kullanılmasının gerekmesinden gelir. Örneğin, belirli bir C++ araç zinciri belirli bir işletim sisteminde çalışabilir ve diğer bazı işletim sistemlerini hedefleyebilir. Bazel, kümenin yürütmesine ve hedef platforma göre kullanılan C++ derleyicisini belirlemelidir (araç zincirleri için belgeleri burada bulabilirsiniz).
Bunu yapmak için araç zincirlerine, destekledikleri yürütme ve hedef platform kısıtlamaları kümesiyle ilgili ek açıklamalar eklenir. Bunu yapmak için araç zincirinin tanımı iki bölüme ayrılır:
- Yürütme kümesini ve hedefi tanımlayan
toolchain()
kuralı, araç zincirinin desteklediğini ve ne tür (C++ veya Java) olduğunu (ör. C++ veya Java) söyler (ikincisi,toolchain_type()
kuralıyla temsil edilir). - Gerçek araç zincirini açıklayan dile özgü bir kural (ör.
cc_toolchain()
)
Bu şekilde yapılır çünkü araç zinciri çözümlemesi yapmak için her araç zincirinin kısıtlamalarını bilmemiz gerekir. Dile özgü *_toolchain()
kuralları bundan çok daha fazla bilgi içerir ve bu nedenle yüklenmeleri daha uzun sürer.
Yürütme platformları, aşağıdaki yollardan biriyle belirtilir:
- WORKSPACE dosyasında
register_execution_platforms()
işlevini kullanarak - --extra_execution_platforms komut satırı seçeneğini kullanarak komut satırında
Kullanılabilir yürütme platformları grubu şurada hesaplanır:RegisteredExecutionPlatformsFunction
Yapılandırılmış bir hedefin hedef platformu PlatformOptions.computeTargetPlatform()
tarafından belirlenir . Bu bir platformlar listesidir çünkü zaman içinde birden çok hedef platformu desteklemek isteyeceğiz, ancak henüz uygulanmadı.
Yapılandırılmış bir hedef için kullanılacak araç zincirleri grubu ToolchainResolutionFunction
tarafından belirlenir. Şunun bir fonksiyonudur:
- Kayıtlı araç zincirleri grubu (WORKSPACE dosyasında ve yapılandırmada)
- İstenen yürütme ve hedef platformlar (yapılandırmada)
- Yapılandırılmış hedefin gerektirdiği araç zinciri türleri grubu (
UnloadedToolchainContextKey)
içinde UnloadedToolchainContextKey
içindeki yapılandırılan 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, temelde araç zinciri türünden (ToolchainTypeInfo
örneği olarak temsil edilir) seçilen araç zincirinin etiketine giden bir harita olan UnloadedToolchainContext
elde edilir. Alet zincirlerinin kendisini değil, yalnızca etiketlerini içermesi nedeniyle "unload" olarak adlandırılmıştır.
Daha sonra araç zincirleri, ResolvedToolchainContext.load()
kullanılarak yüklenir ve bunları isteyen yapılandırılmış hedefin uygulanması tarafından kullanılır.
Ayrıca, --cpu
gibi çeşitli yapılandırma işaretleriyle temsil edilen tek bir "ana makine" yapılandırması ve hedef yapılandırmaları olmasını gerektiren eski bir sistemimiz de mevcuttur . Kademeli olarak yukarıdaki sisteme geçiyoruz. Kullanıcıların eski yapılandırma değerlerinden yararlandığı durumları ele almak için eski işaretler ile yeni stil platform kısıtlamaları arasında geçiş yapmak üzere platform eşlemeleri uyguladık.
Kodu PlatformMappingFunction
dilinde ve Starlark dışında bir "küçük dil" kullanıyor.
Sınırlamalar
Bazen bir hedefin yalnızca birkaç platformla uyumlu olduğunu belirtmek isteriz. Bazel ne yazık ki bu amaca ulaşmak için birden çok mekanizmaya sahiptir:
- Kurala özel 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ılmak üzeredir ve Bazel'de kullanılamaz, ancak kaynak kodu bunlara referanslar içerebilir. Bunu yöneten özelliğe constraints=
adı verilir .
Çevre_grubu() ve ortam()
Bu kurallar eski bir mekanizmadır ve yaygın olarak kullanılmamaktadır.
Tüm derleme kuralları, hangi "ortamlar" için oluşturulabileceğini tanımlayabilir. Burada "ortam", environment()
kuralının bir örneğidir.
Bir kural için desteklenen ortamların belirtilebileceği çeşitli yollar vardır:
restricted_to=
özelliği aracılığıyla. Bu, en doğrudan ayrıntılandırma biçimidir; kuralın bu grup için desteklediği tam ortam grubunu belirtir.compatible_with=
özelliği aracılığıyla. Bu işlem, bir kuralın varsayılan olarak desteklenen "standart" ortamlara ek olarak desteklediği ortamları tanımlar.- 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 benzer bir gruba ("CPU mimarileri", "JDK sürümleri" veya "mobil işletim sistemleri" gibi) aittir. Ortam grubu tanımı, bu ortamlardan hangilerininrestricted_to=
/environment()
özellikleriyle aksi belirtilmediği takdirde "varsayılan" tarafından desteklenmesi gerektiğini içerir. Bu tür özelliklere sahip olmayan bir kural, tüm varsayılanları devralır.- Kural sınıfı varsayılanı üzerinden. 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 bir örneğin açıkça belirtilmesine gerek kalmadan tüm
*_test
kurallarını test edilebilir hale getirmek için kullanılabilir.
environment()
normal bir kural olarak uygulanırken environment_group()
hem Target
alt sınıfıdır (Rule
(EnvironmentGroup
) değildir) hem de varsayılan olarak Starlark'tan (StarlarkLibrary.environmentGroup()
) sunulan bir işlevdir ve sonuçta adsız bir hedef oluşturur. Bunun amacı, her ortamın ait olduğu ortam grubunu ve her ortam grubunun varsayılan ortamlarını bildirmesi gerektiği için ortaya çıkacak döngüsel bağımlılığı önlemektir.
--target_environment
komut satırı seçeneği kullanılarak derleme belirli bir ortamla kısıtlanabilir.
Kısıtlama kontrolünün uygulanması RuleContextConstraintSemantics
ve TopLevelConstraintSemantics
dillerindedir.
Platform kısıtlamaları
Bir hedefin hangi platformlarla uyumlu olduğunu açıklamanın şu anki "resmi" yolu, araç zincirlerini ve platformları tanımlamak için kullanılan kısıtlamaların aynısını kullanmaktır. #10945 pull isteğinde inceleniyor.
Görünürlük
Çok sayıda geliştiriciyle (Google'da olduğu gibi) birlikte büyük bir kod tabanı üzerinde çalışıyorsanız herkesin kodunuza bağlı olarak rastgele çalışmasını engellemeye özen göstermek istersiniz. Aksi takdirde, Hyrum yasası uyarınca kullanıcılar, uygulama ayrıntıları olarak kabul ettiğiniz davranışlardan alınacaktır.
Bazel, bunu görünürlük adı verilen mekanizma ile destekler: Belirli bir hedefin yalnızca görünürlük özelliğini kullanarak bağlı olabileceğini belirtebilirsiniz. Bu özellik biraz özeldir çünkü bu etiket, bir etiket listesi içermesine rağmen, belirli bir hedefe işaret eden bir işaretçi yerine paket adları üzerinde bir kalıp kodlayabilir. (Evet, bu bir tasarım hatasıdır.)
Bu uygulama 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) veya bir 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. İstesek bile bunları basit kurallarla değiştirebiliriz. Bunların mantığı,//pkg/...
gibi tek bir kalıpa karşılık gelenPackageSpecification
; tek birpackage_group
packages
özelliğine karşılık gelenPackageGroupContents
vepackage_group
ile geçişliincludes
öğesi üzerinde toplananPackageSpecificationProvider
kullanılarak uygulanır. - Görünürlük etiketi listelerinden bağımlılıklara dönüştürme işlemi
DependencyResolver.visitTargetVisibility
ve diğer birkaç yerde gerçekleştirilir. - Asıl kontrol
CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()
adımında yapılıyor
İç içe yerleştirilmiş kümeler
Yapılandırılmış bir hedef genellikle bağımlılıklarından bir dosya grubu toplar, kendine ekler ve toplu kümeyi geçişli bilgi sağlayıcısına sarmalar. Böylece, buna bağımlı yapılandırılmış hedefler de aynısını yapabilir. Örnekler:
- Derleme için kullanılan C++ üstbilgi dosyaları
cc_library
geçişinin geçişli kapanışı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ı grubu
Bunu, örneğin List
veya Set
kullanarak basit şekilde yaparsak ikinci dereceden bellek kullanımına
sahip oluruz: N kural zinciri varsa ve her kural bir dosya eklerse 1+2+...+N koleksiyon üyemiz olur.
Bu sorunu gidermek için NestedSet
kavramını geliştirdik. Diğer NestedSet
örneklerinden ve kendilerine ait bazı üyelerden oluşan bir veri yapısıdır. Böylece, kümelerin yönlendirilmiş bir döngüsel grafiğini oluşturur. Bunlar sabittir ve üyeleri tekrar tekrar oluşturulabilir. Çoklu yineleme sırası (NestedSet.Order
): preorder, postorder, topolojik (düğüm her zaman üstlerinden sonra gelir) ve "umursamaz, 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 gösterilir. Bu grafikler, "eylem grafiği" adı verilen, iki taraflı, yönlendirilmiş, çembersel bir grafik halinde düzenlenmiştir.
Yapılar iki türde sunulur: kaynak yapılar (Bazel yürütmeye başlamadan önce kullanılabilir olanlar) ve türetilmiş yapılar (derlenmesi gerekenler). Türetilmiş yapıların kendileri birden fazla türde olabilir:
- **Düzenli eserler. **Bunların güncelliği, sağlamaları hesaplanarak güncelliği kontrol edilir. Kısayol olarak mtime kullanılır; saati değişmediyse dosyanın toplamını sunmayız.
- Düzeltilmemiş sembolik bağlantı yapıları. Bunlar, readlink() işlevi kullanılarak güncellik açısından kontrol edilir. Normal yapılardan farklı olarak, bunlar sallanan sembolik bağlantılar olabilir. Genellikle kullanıcının bazı dosyaları bir tür arşivde 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ıkları kontrol edilir. Bunlar bir
TreeArtifact
olarak temsil edilir. - Sabit meta veri yapıları. Bu yapılarda yapılan değişiklikler yeniden derlemeyi tetiklemez. Bu, yalnızca yapı damgası bilgileri için kullanılır: Sırf geçerli saat değiştiği için yeniden oluşturma yapmak istemeyiz.
Kaynak yapılarının ağaç yapıları veya çözülmemiş sembolik bağlantı yapıları olmamasının temel bir nedeni yoktur. Sadece onu henüz uygulamamış olmamızdır (ancak BUILD
dosyasında bir kaynak dizine referans vermek, Bazel'de uzun süredir var olan birkaç hatalılık sorunundan biridir. BAZEL_TRACK_SOURCE_DIRECTORIES=1
JVM özelliği tarafından etkinleştirilen bu tür bir
uygulamamız vardır)
Önemli Artifact
türü aracılardır. Bunlar, MiddlemanAction
çıktıları olan Artifact
örnekleriyle gösterilir. Bunlar bazı şeyleri
özelleştirmek için kullanılır:
- Toplama aracıları, yapıları birlikte gruplandırmak için kullanılır. Bunun nedeni, birçok işlem aynı büyük giriş grubunu kullanıyorsa N*M bağımlılık kenarlarımızın olmaması, yalnızca N+M (bunlar iç içe kümelerle değiştirilecektir)
- Planlama bağımlılık aracıları, bir işlemin diğerinden önce çalışmasını sağlar.
Çoğunlukla hata ayıklama için kullanılırlar, ancak aynı zamanda C++ derlemesi için de kullanılırlar (açıklama için
CcCompilationContext.createMiddleman()
bölümüne bakın) - Runfiles aracıları, bir runfiles ağacının varlığını sağlamak için kullanılır. Böylece, ayrı olarak çıkış manifestine ve runfiles ağacı tarafından başvurulan her bir yapıya bağımlı olmasına gerek kalmaz.
Eylemler; çalıştırılması gereken komutlar, komutun ihtiyaç duyduğu ortam ve ürettiği çıkış kümesi olarak tanımlanabilir. Bir işlem 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ışması gereken ortamı (ör. platform) açıklayan ek açıklamalar \
İçeriği Bazel tarafından bilinen bir dosya yazmak gibi başka özel durumlar da vardır. Bunlar AbstractAction
sınıfının bir 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 bir SpawnAction
veya StarlarkAction
'dir (aynı şekilde, muhtemelen ayrı sınıflar olmamalıdır).
Nihayetinde her şeyi SpawnAction
konumuna taşımak isteriz. JavaCompileAction
çok yakın bir çözüm ama .d dosyası ayrıştırma ve taramayı dahil etme nedeniyle C++ biraz özel bir durum.
Eylem grafiği Skyframe grafiğine çoğunlukla "yerleştirilmiştir": Kavram olarak, bir işlemin yürütülmesi ActionExecutionFunction
çağrısı olarak temsil edilir. Bir işlem grafiği bağımlılık ucundan Skyframe bağımlılık kenarına 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 vardır:
- Türetilen yapıların kendi
SkyValue
'leri yoktur. Bunun yerine, bunu oluşturan eylemin anahtarını bulmak içinArtifact.getGeneratingActionKey()
kullanılır - İç içe yerleştirilmiş grupların 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ının türetilmiş işlemlerini yalnızca yapılandırmalarına ve paketlerine göre belirlenen bir dizine yerleştirmelerine (ancak aynı paketteki kurallar çakışsa bile) izin verildiği için Starlark kurallarının daha sınırlı olması gerekir. Ancak, Java'da uygulanan kurallar türetilmiş yapıları herhangi bir yere yerleştirebilir.
Bu, yanlış bir özellik olarak kabul edilir, ancak bundan kurtulmak çok zordur. Çünkü, örneğin, bir kaynak dosyanın bir şekilde işlenmesi gerektiğinde ve söz konusu dosyaya birden çok kuralla (handwave-handwave) referans verildiğinde yürütme süresinde önemli ölçüde tasarruf sağlanır. Bunun için bir miktar RAM kullanılır: Paylaşılan işlem her örneğinin bellekte ayrı olarak depolanması gerekir.
İki işlem aynı çıkış dosyasını oluşturuyorsa bunların tamamen aynı olması gerekir: Aynı girişlere ve aynı çıkışlara sahip olmalı ve aynı komut satırını çalıştırmalıdır. Bu denklik ilişkisi Actions.canBeShared()
öğesinde uygulanır ve her işleme bakılarak analiz ve yürütme aşamaları arasında doğrulanır.
Bu uygulama SkyframeActionExecutor.findAndStoreArtifactConflicts()
ürününde uygulanır ve Bazel'de yapının "global" bir görünümünü gerektiren birkaç yerden biridir.
Yürütme aşaması
Bu noktada Bazel, çıkış üreten komutlar gibi derleme işlemlerini çalıştırmaya başlar.
Bazel'ın analiz aşamasından sonra yaptığı ilk şey hangi Yapıların oluşturulması
gerektiğini belirlemektir. Bunun mantığı TopLevelArtifactHelper
olarak kodlanmıştır. Özetle, "bu hedef komut satırındaysa bu yapıları oluştur" ifadesinin açıkça ifade edilmesi amacıyla komut satırındaki yapılandırılmış hedeflerin filesToBuild
ve özel bir çıkış grubunun içeriğidir.
Bir sonraki adım yürütme kökü oluşturmaktır. Bazel, dosya sisteminde (--package_path
) farklı konumlardaki kaynak paketleri okuma seçeneğine sahip olduğundan, yerel olarak yürütülen işlemleri tam bir kaynak ağacıyla sağlaması gerekir. Bu, SymlinkForest
sınıfı tarafından yönetilir ve analiz aşamasında kullanılan her hedef dikkate alınarak ve her paketi asıl konumundan kullanılan bir hedefle sembolikleştiren tek bir dizin ağacı oluşturarak çalışır. Bir alternatif, komutlara doğru yolları iletmektir (--package_path
dikkate alınarak).
Bu durum istenmeyen bir durumdur çünkü:
- Bir paket, paket yolu girişinden diğerine taşındığında işlem komut satırlarını değiştirir (bu genellikle yaygın bir durumdur)
- Bir işlem uzaktan çalıştırıldığında yerel olarak çalıştırıldığında farklı komut satırlarıyla sonuçlanır
- Kullanımdaki araca özel bir komut satırı dönüşümü gerektirir (Java sınıf yolları ile C++ yolları arasındaki farka dikkat edin)
- Bir işlemin komut satırı değiştirildiğinde, işlem önbelleği girişi geçersiz hale gelir
--package_path
kademeli olarak kullanımdan kaldırılıyor
Ardından Bazel, işlem grafiğinde (eylemler ve bunların giriş/çıkış yapılarından oluşan iki taraflı, yönlendirilmiş grafik) gezinmeye ve işlemleri yürütmeye başlıyor.
Her işlemin yürütülmesi SkyValue
ActionExecutionValue
sınıfının bir örneğiyle temsil edilir.
Bir işlem çalıştırmak pahalı olduğundan, Skyframe'in arkasında tutulabilecek birkaç önbelleğe alma katmanımız var:
ActionExecutionFunction.stateMap
, Skyframe yeniden başlatma işlemleriniActionExecutionFunction
tutarında ucuza yapmak için veri içeriyor- Yerel işlem önbelleği, dosya sisteminin durumuyla ilgili veriler 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 çalıştırılsa bile yerel işlem önbelleğinde isabet olabilir. Yerel dosya sisteminin durumunu temsil eder ve diske serilenir. Yani, yeni bir Bazel sunucusu başlatıldığında Skyframe grafiği boş olsa bile yerel işlem önbelleği isabetleri alınabilir.
Bu önbellek, şu yöntem kullanılarak isabet olup olmadığı kontrol edilir:
ActionCacheChecker.getTokenIfNeedToExecute()
.
Adının aksine, türetilmiş bir eserin izlediği yoldan onu yayan eyleme kadar uzanan bir haritadır. İşlem şu şekilde açıklanır:
- Giriş ve çıkış dosyaları ve bunların sağlamaları kümesi
- Genellikle yürütülen komut satırı olan "işlem anahtarı", giriş dosyalarının toplamı tarafından yakalanmayan her şeyi temsil eder (örneğin,
FileWriteAction
için yazılan verilerin sağlamasıdır).
Ayrıca hâlâ geliştirilme aşamasında olan ve çok sayıda önbelleğe gitmekten kaçınmak için geçişli karmalar kullanan, son derece deneysel bir "yukarıdan aşağıya işlem önbelleği" vardır.
Giriş bulma ve giriş ayıklama
Bazı işlemler, yalnızca bir dizi girişe sahip olmaktan daha karmaşıktır. Bir işlemin giriş grubunda yapılan değişiklikler iki biçimde gerçekleşir:
- Bir eylem, yürütme işleminden önce yeni girişler keşfedebilir veya bazı girişlerinin aslında gerekli olmadığına karar verebilir. Google'ın standart örneği C++'dır. "Burada, C++ dosyasının geçişli kapanışında hangi başlık dosyalarını kullandığına dair bilgiye dayalı bir tahminde bulunmak daha iyidir.
Böylece her dosyayı
#include
- Bir işlem, yürütme sırasında bazı dosyaların kullanılmadığını fark edebilir. C++'da buna ".d dosyaları" adı verilir: Derleyici, hangi başlık dosyalarının olaydan sonra kullanıldığını bildirir ve Bazel, Make'den daha kötü bir artımlılığa sahip olmanın mahcup olmasını önlemek için bu olguyu kullanır. Derleyiciye bağlı olduğu için bu, include tarayıcısından daha iyi bir tahmin sunar.
Bunlar, Action üzerindeki yöntemler kullanılarak uygulanır:
Action.discoverInputs()
çağrıldı. Gerekli olduğu belirlenen iç içe yerleştirilmiş bir Yapı grubu döndürmelidir. Bunlar kaynak yapılar olmalıdır. Böylece işlem grafiğinde, yapılandırılmış hedef grafiğinde eşdeğeri olmayan bağımlılık kenarları bulunmaz.- İşlem,
Action.execute()
çağrısı yapılarak yürütülür. Action.execute()
işleminin sonunda işlem, Bazel'a tüm girişlerinin gerekli olmadığını bildirmek içinAction.updateInputs()
işlevini çağırabilir. Kullanılan bir girişin kullanılmadığı bildirilirse bu durum hatalı artımlı derlemelere neden olabilir.
Bir işlem önbelleği yeni bir İşlem örneğine (sunucu yeniden başlatıldıktan sonra oluşturulan gibi) bir isabet döndürdüğünde Bazel, giriş kümesinin daha önce yapılan giriş keşfi ve budanma işleminin sonucunu yansıtması için updateInputs()
kendisini çağırır.
Starlark işlemleri, ctx.actions.run()
öğesinin unused_inputs_list=
bağımsız değişkenini kullanarak bazı girişleri kullanılmamış olarak tanımlamak için bu özellikten yararlanabilir.
İşlemleri yürütmenin çeşitli yolları: Stratejiler/İşlem Bağlamları
Bazı işlemler farklı şekillerde çalıştırılabilir. Örneğin, bir komut satırı yerel olarak, ancak çeşitli korumalı alanlarda veya uzaktan yürütülebilir. Bunu içtenActionContext
Strategy
İş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 eylem bağlamlarına sahip oldukları sorulur. Bu,ExecutionTool
oluşturucuda gerçekleşir. İşlem bağlamı türleri,ActionContext
alt arayüzünü ifade eden ve işlem bağlamının uygulanması gereken arayüzü olan bir JavaClass
örneği tarafından tanımlanır. - Mevcut olanlardan uygun işlem bağlamı seçilerek
ActionExecutionContext
veBlazeExecutor
'a yönlendirilir . - İşlemler,
ActionExecutionContext.getContext()
veBlazeExecutor.getStrategy()
kullanan bağlamları istiyor (gerçekten bunu yapmanın tek bir yolu olmalıdır...)
Stratejiler, işlerini yapmaları için diğer stratejileri çağırmakta serbesttir. Bu, örneğin hem yerel hem de uzaktan işlem başlatan ve daha sonra hangi işlemi önce biterken kullanan dinamik stratejide kullanılır.
Kayda değer stratejilerden biri kalıcı çalışan süreçleri (WorkerSpawnStrategy
) uygulayan stratejidir. Bu fikir, bazı araçların uzun başlatma süresinin olduğu ve bu nedenle her işlem için yeniden başlatmak yerine eylemler arasında yeniden kullanılması gerektiğidir (Bazel, çalışan sürecinin bireysel istekler arasında gözlemlenebilir durum taşımayacağı vaadine bağlı olduğu için bu olası bir doğruluk sorunu teşkil eder.)
Araç değişirse çalışan işlemin 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. İşlemin hangi girişlerinin aracın parçasını, hangilerinin ise girişleri temsil ettiğinin bilinmesine bağlıdır. Bu, İşlemi oluşturan kişi tarafından belirlenir: Spawn.getToolFiles()
ve Spawn
çalıştırma dosyaları aracın bölümleri olarak sayılır.
Stratejiler (veya eylem bağlamları!) hakkında daha fazla bilgi edinin:
- İşlem yürütmeye yönelik çeşitli stratejilerle ilgili bilgileri burada bulabilirsiniz.
- Burada, hangisinin önce tamamlandığını görmek için hem yerel olarak hem de uzaktan işlem yaptığımız dinamik strateji hakkında bilgi edinebilirsiniz.
- İş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şleme göre değişir: Bir işlem ne kadar çok kaynak gerektirirse yerel makineye aşırı yüklenilmesini önlemek için aynı anda daha az örneğin çalıştırılması gerekir.
Bu işlem ResourceManager
sınıfında uygulanır: Her işlem, gerektirdiği yerel kaynakların tahmini bir ResourceSet
örneği (CPU ve RAM) biçiminde ek açıklama olarak eklenmelidir. Daha sonra işlem bağlamları yerel kaynak gerektiren bir işlem yaptığında, ResourceManager.acquireResources()
yöntemini çağırır ve gerekli kaynaklar kullanılabilir olana kadar engellenir.
Yerel kaynak yönetimiyle ilgili daha ayrıntılı bir açıklamayı burada bulabilirsiniz.
Çıkış dizininin yapısı
Her işlem, çıktıların yerleştirildiği çıkış dizininde ayrı bir yere ihtiyaç duyar. Türetilen yapıların konumu genellikle aşağıdaki gibidir:
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
Belirli bir yapılandırmayla ilişkili dizinin adı nasıl belirlenir? Çelişen iki istenen özellik vardır:
- Aynı derlemede iki yapılandırma oluşabiliyorsa bunların her ikisi de farklı dizinlere sahip olmalıdır. Böylece her iki yapılandırma da aynı çıkış dosyasını üreten bir işlemin komut satırı üzerinde anlaşamazsa, Bazel hangi işlemi seçeceğini ("işlem çakışması") bilemez.
- İki yapılandırma "kabaca" aynı şeyi temsil ediyorsa komut satırları eşleşirse birinde yürütülen işlemlerin diğeri için yeniden kullanılabilmesi için aynı ada sahip olması gerekir: Örneğin, Java derleyicisinde komut satırı seçeneklerinde yapılan değişiklikler, C++ derleme işlemlerinin yeniden çalıştırılmasına neden olmamalıdır.
Şu ana kadar bu sorunu çözmek için yapılandırma kırpma sorunuyla benzerlikler taşıyan ilkeli bir yol bulamadık. Seçeneklerle ilgili daha uzun bir açıklamayı burada bulabilirsiniz. Başlıca sorunlu alanlar, “aynı" çıktı dosyasını üretebilecek unsurlar alanına başka bir boyut ekleyen Starlark kuralları (yazarları genelde Bazel’e pek aşina değildir) ve oranlardır.
Mevcut yaklaşımda yapılandırmanın yol segmenti, çeşitli son eklerle birlikte <CPU>-<compilation mode>
şeklindedir. Böylece Java'da uygulanan yapılandırma geçişleri, işlem çakışmalarına neden olmaz. Ayrıca, kullanıcıların işlem çakışmalarına yol açmaması için Starlark yapılandırma geçişleri kümesinin bir sağlama toplamı eklenir. Mükemmel değil. Bu işlem, OutputDirectories.buildMnemonic()
içinde uygulanır ve her yapılandırma parçasının çıkış dizini adına kendi parçasını eklemesine dayanır.
Testler
Bazel, test çalıştırma konusunda kapsamlı destek sunar. Şunları destekler:
- Testleri uzaktan çalıştırma (uzaktan yürütme arka ucu varsa)
- Testleri paralel olarak birden çok kez çalıştırma (zamanlama verilerini azaltmak veya toplamak için)
- Parçalama testleri (hız için aynı testte test durumlarını birden fazla işleme bölme)
- Kesintili testleri yeniden çalıştırma
- Testleri test paketlerinde gruplandırma
Testler, testin nasıl çalıştırılması gerektiğini açıklayan bir TestProvider öğesine sahip normal yapılandırılmış hedeflerdir:
- Yapısı, testin çalıştırılmasıyla sonuçlanan yapılar. Bu, serileştirilmiş
TestResultData
mesajı içeren bir "önbellek 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 yürütüleceğini belirlemek titiz bir süreçtir.
İlk olarak, hedef kalıp ayrıştırma sırasında test paketleri tekrarlı olarak genişletilir. Genişletme işlemi TestsForTargetPatternFunction
politikasında uygulanır. Biraz şaşırtıcı bir şekilde, test paketinin hiçbir test belirtmezse paketindeki her teste atıfta bulunması da şaşırtıcıdır. Bu özellik, paket kurallarını test etmek için $implicit_tests
adlı dolaylı bir özellik eklenerek Package.beforeBuild()
ürününde uygulanır.
Ardından testler, komut satırı seçeneklerine göre boyut, etiket, zaman aşımı ve dile göre filtrelenir. Bu, TestFilter
içinde uygulanır, hedef ayrıştırma sırasında TargetPatternPhaseFunction.determineTests()
öğesinden çağrılır ve sonuç TargetPatternPhaseValue.getTestsToRunLabels()
içine yerleştirilir. Filtrelenebilir kural özelliklerinin yapılandırılamamasının nedeni, bunun analiz aşamasından önce gerçekleşmesi ve bu nedenle yapılandırmanın mevcut olmamasıdır.
Bu işlem, daha sonra BuildView.createResult()
içinde daha ayrıntılı bir şekilde işlenir: Analizi başarısız olan hedefler filtrelenir ve testler, özel ve münhasır olmayan testlere bölünür. Daha sonra, ExecutionTool
hangi testlerin çalıştırılacağını
bilmek için AnalysisResult
içine yerleştirilir.
Bu ayrıntılı işleme biraz şeffaflık kazandırmak amacıyla, komut satırında belirli bir hedef belirtildiğinde hangi testlerin çalıştırıldığını tests()
sorgu operatörü (TestsFunction
uygulamasında uygulanır) kullanabilir. Ne yazık ki bu bir yeniden uygulama, dolayısıyla muhtemelen birkaç farklı şekilde yukarıdakilerden sapmaktadır.
Testler yapma
Testler, önbellek durumu yapıları istenerek çalıştırılır. Bu daha sonra bir TestRunnerAction
yürütülmesiyle sonuçlanır. Bu da sonunda, testi istenen şekilde çalıştıran --test_strategy
komut satırı seçeneği tarafından seçilen TestActionContext
öğesini çağırır.
Testler, testlere kendilerinden ne beklendiğini bildirmek için ortam değişkenlerini kullanan ayrıntılı bir protokole göre çalıştırılır. Bazel'ın testlerden neler beklediği ve testlerin Bazel'den neler bekleyebileceğiyle ilgili ayrıntılı açıklamayı burada bulabilirsiniz. En basit ifadeyle, 0 çıkış kodu başarı, diğer her şey başarısızlık anlamına gelir.
Her test işlemi, önbellek durumu dosyasına ek olarak bir dizi başka dosya yayınlar. Bunlar, hedef yapılandırmanın çıkış dizininin testlogs
adlı alt dizini olan "test günlüğü dizinine" yerleştirilir:
test.xml
, test parçasındaki her bir test durumunu ayrıntılı şekilde açıklayan bir JUnit stili XML dosyası- test. stdout ve stderr çıkışının konsol çıkışı (
test.log
) ayrılmaz. test.outputs
, "bildirilmemiş çıkış dizini"dir. Bu, terminale yazdırdıkları verilerin yanı sıra dosyaların çıkışını da almak isteyen testler tarafından kullanılır.
Test yürütülürken normal hedefler oluşturulurken mümkün olmayan iki şey gerçekleşebilir: özel test yürütme ve çıkış akışı.
Bazı testlerin özel modda yürütülmesi gerekir (örneğin, diğer testlere paralel olarak değil). Bu, test kuralına tags=["exclusive"]
eklenmesi veya testi --test_strategy=exclusive
ile çalıştırılmasıyla elde edilebilir . Her özel test, "ana" derlemeden sonra testin yürütülmesini isteyen ayrı bir Skyframe çağrısı ile çalıştırılır. Bu uygulama SkyframeExecutor.runExclusiveTest()
politikasında uygulanmıştır.
İşlem tamamlandığında terminal çıkışı düşen normal işlemlerin aksine kullanıcı, uzun süreli bir testin ilerleme durumu hakkında bilgi almak için test çıktısının akış şeklinde yayınlanmasını isteyebilir. Bu, --test_output=streamed
komut satırı seçeneğiyle belirtilir ve farklı test çıkışlarının serpiştirilmemesi için özel test yürütmeyi gerektirir.
Bu işlem, tam olarak adlandırılmış StreamedTestOutput
sınıfında uygulanır ve söz konusu testin test.log
dosyasındaki değişiklikler sorgulanarak ve yeni baytlar Bazel'in kurallarının bulunduğu terminale atanarak çalışır.
Yürütülen testlerin sonuçları, çeşitli etkinliklerin (TestAttempt
, TestResult
veya TestingCompleteEvent
gibi) gözlemlenmesiyle etkinlik veri yolunda bulunur. Bu testler, Derleme Etkinliği Protokolü'ne aktarılır ve AggregatingTestListener
tarafından konsola yayınlanır.
Kapsam koleksiyonu
Kapsam, dosyalardaki LCOV biçiminde yapılan testler tarafından raporlanır.
bazel-testlogs/$PACKAGE/$TARGET/coverage.dat
Kapsamı toplamak için her test yürütmesi collect_coverage.sh
adlı bir komut dosyasında sarmalanır .
Bu komut dosyası, kapsam toplamayı etkinleştirmek ve kapsam dosyalarının kapsam çalışma zamanları tarafından nereye yazıldığını belirlemek için test ortamını oluşturur. Ardından, testi çalıştırır. Bir test birden fazla alt işlem çalıştırabilir ve birden fazla farklı programlama dilinde (ayrı kapsam toplama çalışma zamanlarıyla) yazılmış parçalardan oluşabilir. Sarmalayıcı komut dosyası, gerektiğinde sonuç dosyalarını LCOV biçimine dönüştürmekten sorumludur ve bunları tek bir dosyada birleştirir.
collect_coverage.sh
etkileşimi test stratejileri tarafından yapılır ve test girişlerinde collect_coverage.sh
öğesinin olması gerekir. Bu işlem, --coverage_support
yapılandırma işaretinin değerine çözümlenen örtülü :coverage_support
özelliği ile gerçekleştirilir (bkz. TestConfiguration.TestOptions.coverageSupport
)
Bazı diller çevrimdışı araçlar kullanır. Yani kapsam enstrümantasyonu, derleme sırasında eklenir (C++ gibi) ve diğerleri online araçlar kullanır. Yani kapsam araçları yürütme sırasında eklenir.
Bir diğer temel kavram ise referans kapsamı'dır. Bu; kitaplık, ikili program veya içinde hiçbir kodun çalıştırılıp çalıştırılmadığı test edilmesini kapsar. Çözümün çözdüğü sorun, bir ikili program için test kapsamını hesaplamak istiyorsanız tüm testlerin kapsamını birleştirmek yeterli olmayacaktır. Çünkü ikili programda herhangi bir teste bağlı olmayan kod olabilir. Bu nedenle, yalnızca kapsam topladığımız dosyaları içeren ve kapsama dahil olmayan satırlar içermeyen her ikili program için bir kapsam dosyası yayınlarız. Hedef için referans kapsam dosyası şudur: bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat
--nobuild_tests_only
işaretini Bazel'e geçirmeniz durumunda testlerin yanı sıra ikili programlar ve kitaplıklar için de oluşturulur.
Temel kapsam şu anda kullanılamıyor.
Her kuralda kapsam koleksiyonu için iki dosya grubu izlenir: araçlı dosyalar grubu ve araç meta veri dosyaları grubu.
Araçlı dosyalar kümesi, tam da bir araç setinden ibarettir. Online kapsam çalışma zamanlarında bu, hangi dosyaların kullanılacağına karar vermek için çalışma zamanında kullanılabilir. Temel kapsamı uygulamak için de kullanılır.
Araç meta veri dosyaları grubu, bir testin Bazel'ın dosyadan ihtiyaç duyduğu LCOV dosyalarını oluşturmak için ihtiyaç duyduğu ekstra dosyalar kümesidir. Pratikte bu, çalışma zamanına özel dosyalardan oluşur. Örneğin, gcc, derleme sırasında .gcno dosyaları yayar. Kapsam modu etkinleştirilirse bunlar, test işlemleri giriş grubuna eklenir.
Kapsamın toplanıp toplanmayacağı BuildConfiguration
özelliğinde depolanır. Bu, test işlemini ve eylem grafiğini bu bite bağlı olarak değiştirmenin kolay bir yolu olduğu için faydalıdır. Aynı zamanda, bu bit çevrilirse tüm hedeflerin yeniden analiz edilmesi gerektiği anlamına da gelir (C++ gibi bazı diller, kapsamı toplayabilecek bir kod oluşturmak için farklı derleyici seçenekleri gerektirir ve bu da yine de yeniden analiz yapılması gerektiğinden bu sorunu bir şekilde azaltır).
Kapsam destek dosyaları, örtülü bir bağımlılıkta etiketlerle kullanılır. Bu sayede çağrı politikası, farklı Bazel sürümleri arasında farklılık gösterebilmelerini sağlayan çağrı politikası tarafından geçersiz kılınabilir. İdeal olan, bu farklılıkların ortadan kaldırılmasıdır ve biz de bunlardan birini standartlaştırdık.
Ayrıca, bir Bazel çağrısındaki her test için toplanan kapsamı birleştiren bir "kapsam raporu" oluştururuz. Bu işlem CoverageReportActionFactory
tarafından işlenir ve BuildView.createResult()
tarafından çağrılır . Yürütülen ilk testin :coverage_report_generator
özelliğine bakarak ihtiyaç duyduğu araçlara erişim sağlar.
Sorgu motoru
Bazel'ın çeşitli grafikler hakkında çeşitli sorular sormak için küçük bir dili var. Aşağıdaki sorgu türleri sunulur:
bazel query
, hedef grafiği incelemek için kullanılırbazel cquery
, yapılandırılmış hedef grafiğini incelemek için kullanılırbazel aquery
, işlem grafiğini incelemek için kullanılır
Bunların her biri, AbstractBlazeQueryEnvironment
alt sınıfına göre uygulanır.
QueryFunction
alt sınıflandırma yaparak ek ek sorgu işlevleri yapılabilir. Sorgu akışı sonuçlarına izin vermek amacıyla sonuçları bir veri yapısında toplamak yerine, döndürmek istediği sonuçlar için çağıran QueryFunction
işlevine bir query2.engine.Callback
iletilir.
Bir sorgunun sonucu çeşitli şekillerde yayınlanabilir: Etiketler, etiketler ve kural sınıfları, XML, protobuf vb. Bunlar, OutputFormatter
alt sınıfları olarak uygulanır.
Bazı sorgu çıktısı biçimlerinin (proto, kesinlikle) küçük bir gerekliliği de, sonucun farklı olup belirli bir hedefin değişip değişmediğini belirleyebilmesi için Bazel'in paket yüklemesinin sağladığı bilgileri _tümünü _aktarmasıdır. Sonuç olarak, özellik değerlerinin seri hale getirilebilmesi gerekir. Bu nedenle, karmaşık Starlark değerlerine sahip öznitelikleri olmayan çok az özellik türü vardır. Olağan geçici çözüm, bir etiket kullanmak ve karmaşık bilgileri bu etikete sahip kurala eklemektir. Bu pek de tatmin edici bir çözüm değildir ve bu koşulu kaldırmak bizi çok memnun eder.
Modül sistemi
Bazel'a modüller eklenerek genişletilebilir. Her modül BlazeModule
alt sınıfı (adı, eskiden Blaze olarak adlandırıldığında Bazel geçmişinin kalıntılarıdır) ve bir komutun yürütülmesi sırasında çeşitli olaylar hakkında bilgi alır.
Bunlar çoğunlukla Bazel'in yalnızca bazı sürümlerinin (Google'da kullandığımız sürüm gibi) ihtiyaç duyduğu, "çekirdek olmayan" işlevlerin çeşitli parçalarını uygulamak için kullanılır:
- Uzaktan yürütme sistemlerine arayüzler
- Yeni komutlar
BlazeModule
tarafından sunulan uzantı noktaları grubu biraz şans eseri değildir. Bunu iyi tasarım ilkelerinin
bir örneği olarak kullanmayın.
Etkinlik otobüsü
BlazeModules'un Bazel'in geri kalanıyla iletişim kurmak için kullandığı ana yöntem bir etkinlik otobüsüdür (EventBus
): Her derleme için yeni bir örnek oluşturulur, Bazel'ın çeşitli bölümleri bu örnekte etkinlik yayınlayabilir ve modüller, ilgilendikleri etkinlikler için işleyicileri 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
) - Bir hedefin oluşturulup oluşturulmadığı (
TargetCompleteEvent
) - Bir test yürütüldü (
TestAttempt
,TestSummary
)
Bu etkinliklerden bazıları, Etkinlik Derleme Protokolü'nde Bazel dışında temsil edilir (BuildEvent
). Bu, sadece BlazeModule
'lerin değil, Bazel işleminin dışındaki öğelerin de derlemeyi gözlemlemesine olanak tanır. Bunlara, protokol mesajları içeren bir dosya olarak erişilebilir veya Bazel, etkinlikleri yayınlamak için bir sunucuya (Derleme Etkinliği Hizmeti denir) bağlanabilir.
build.lib.buildeventservice
ve build.lib.buildeventstream
Java paketlerinde uygulanır.
Harici depolar
Bazel esasen bir monorepoda (inşa edilmesi gereken her şeyi içeren tek kaynak ağaç) kullanılmak üzere tasarlanmış olsa da Bazel bunun her zaman doğru olmadığı bir dünyada yaşıyor. "Harici depolar" bu iki dünyayı birbirine bağlamak için kullanılan bir soyutlamadır: Derleme için gerekli olan ancak ana kaynak ağacında yer almayan kodu temsil ederler.
WORKSPACE dosyası
Harici depo kümesi, WORKSPACE dosyasının ayrıştırılmasıyla belirlenir. Örneğin, şöyle bir beyan:
local_repository(name="foo", path="/foo/bar")
@foo
adlı depodaki sonuçlar kullanılabilir. Bu işin karmaşık hale geldiği nokta, Starlark dosyalarında yeni depo kuralları tanımlayabilmektir. Bu kurallar, daha sonra yeni Starlark kodu yüklemek için kullanılabilir. Bu kodlar yeni depo kuralları tanımlamak vb. için de kullanılabilir.
Bu durumu işlemek için WORKSPACE dosyasının (WorkspaceFileFunction
bölgesinde) ayrıştırılması, load()
ifadeleriyle tanımlanan parçalara bölünür. Yığın dizini WorkspaceFileKey.getIndex()
ile gösterilir ve X dizini için WorkspaceFileFunction
hesaplanırken, X. load()
ifadesine kadar değerlendirme yapılması gerekir.
Kod depoları getiriliyor
Deponun kodu Bazel'ın kullanımına sunulmadan önce fetched gerekir. Bu işlem, Bazel'in $OUTPUT_BASE/external/<repository name>
altında bir dizin oluşturmasına neden olur.
Kod deposunun getirilmesi aşağıdaki adımlarla gerçekleşir:
PackageLookupFunction
, kod deposuna ihtiyacı olduğunu fark eder veSkyKey
olarak birRepositoryName
oluşturur. Bu daRepositoryLoaderFunction
çağrısına yol açar.RepositoryLoaderFunction
, net olmayan nedenlerden dolayı isteğiRepositoryDelegatorFunction
adresine iletir (kodda, Skyframe yeniden başlatıldığında içeriklerin yeniden indirilmesinden kaçınılması gerektiği belirtilse de bu pek mantıklı bir neden değildir)RepositoryDelegatorFunction
, istenen depo bulunana kadar WORKSPACE dosyasının parçalarını iterasyon yaparak getirmesi istenen depo kuralını öğrenir- Depo getirme işlemini uygulayan uygun
RepositoryFunction
bulunur. Bu, deponun Starlark uygulaması veya Java'da uygulanan depolar için sabit kodlu bir haritadır.
Kod deposunu getirmek çok pahalı olabileceğinden, çeşitli önbelleğe alma katmanları vardır:
- İndirilen dosyalar için, sağlama toplamı (
RepositoryCache
) tarafından anahtarlanan bir önbellek vardır. Bu, sağlama toplamının WORKSPACE dosyasında bulunmasını gerektirir, ancak yine de hermetik açıdan iyi sonuç verir. Bu görev, hangi çalışma alanında veya çıkış tabanında çalıştıklarından bağımsız olarak, aynı iş istasyonundaki her Bazel sunucu örneği tarafından paylaşılır. $OUTPUT_BASE/external
altındaki her depo için bir "işaretçi dosyası", kodu getirmek üzere kullanılan kuralın sağlaması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 sunucu yeniden getirilmez. Bu uygulamaRepositoryDelegatorFunction.DigestWriter
politikasında uygulanır .--distdir
komut satırı seçeneği, indirilecek yapıları aramak için kullanılan başka bir önbellek belirtir. Bu, Bazel'ın internetten rastgele öğeler getirmemesi gerektiği kurumsal ayarlarda faydalıdır. Bu yöntem,DownloadManager
tarafından uygulanır .
Depo indirildikten sonra içindeki yapılar kaynak yapılar olarak değerlendirilir. Bu durum bir sorun teşkil eder. Çünkü Bazel genellikle kaynak yapılarının güncelliğini bunlar üzerinde stat() çağrısı yaparak kontrol eder ve bu yapılar, bulundukları depo tanımı değiştiğinde de geçersiz kılınır. Bu nedenle, harici depodaki bir yapı için FileStateValue
'ler, bunların harici deposuna bağlı olmalıdır. Bu işlem ExternalFilesHelper
tarafından yönetiliyor.
Yönetilen dizinler
Bazen harici depoların çalışma alanı kök dizinindeki dosyaları değiştirmesi gerekir (ör. indirilen paketleri kaynak ağacının bir alt dizininde barındıran paket yöneticisi). Bu yaklaşım, Bazel'in kaynak dosyaları tek başına değil, yalnızca kullanıcı tarafından değiştirildiği ve paketlerin, çalışma alanı kökü altındaki her dizine başvuruda bulunmasına izin verdiği varsayımıyla çelişmektedir. Bu tür bir harici deponun çalışmasını sağlamak için Bazel iki şey yapar:
- Kullanıcının, Bazel'ın erişmesine izin verilmeyen çalışma alanının alt dizinlerini belirtmesine izin verir. Bunlar,
.bazelignore
adlı bir dosyada listelenir ve işlevBlacklistedPackagePrefixesFunction
işlevinde uygulanır. - Eşlemeyi, çalışma alanının alt dizininden, işlendiği harici depoya
ManagedDirectoriesKnowledge
içinde kodlar ve bunlara referansta bulunanFileStateValue
öğelerini normal harici depolarla aynı şekilde işleriz.
Depo eşlemeleri
Birden fazla depo aynı depoya bağlı olmak isteyebilir ancak farklı sürümlerde (bu, "elmas bağımlılığı sorunu"nun bir örneğidir) olabilir. Örneğin, derlemedeki ayrı depolardaki iki ikili program Guava'ya bağımlı olmak istiyorsa her ikisi de muhtemelen @guava//
ile başlayan etiketlerle Guava'ya atıfta bulunur ve bunun farklı sürümleri anlamına gelmesini bekler.
Bu nedenle Bazel, birinin harici depo etiketlerini yeniden eşlemesine izin verir. Böylece @guava//
dizesi, bir ikili programın deposundaki bir Guava deposuna (ör. @guava1//
), diğerinin deposundaki başka bir Guava deposuna (@guava2//
gibi) işaret edebilir.
Alternatif olarak bu elmaslara join için de kullanılabilir. Bir depo @guava1//
, bir diğeri @guava2//
kullanıyorsa depo eşlemesi, standart @guava//
deposu kullanmak için her iki deponun yeniden eşlenmesine olanak tanır.
Eşleme, WORKSPACE dosyasında bağımsız depo tanımlarının repo_mapping
özelliği olarak belirtilir. Ardından Skyframe'de WorkspaceFileValue
grubunun üyesi olarak görünür ve burada şuna bağlanır:
Package.Builder.repositoryMapping
etiketi, paketteki kuralların etiket değerli özellikleriniRuleClass.populateRuleAttributeValues()
Package.repositoryMapping
, analiz aşamasında kullanılır (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'da yazılır. İstisna olarak, Java'nın kendi başına ya da biz uygulandığında kendi başına yapamayacağı kısımlardır. Bu, çoğunlukla dosya sistemi, süreç kontrolü ve diğer alt düzey şeylerle etkileşimle sınırlıdır.
C++ kodu src/main/native altında bulunur ve yerel yöntemlere sahip Java sınıfları şunlardır:
NativePosixFiles
veNativePosixFileSystem
ProcessUtils
WindowsFileOperations
veWindowsFileProcesses
com.google.devtools.build.lib.platform
Konsol çıkışı
Konsol çıkışını yaymak basit bir iş gibi görünse de birden fazla işlemin (bazen uzaktan çalıştırılması), ayrıntılı önbelleğe almanın, güzel ve renkli bir terminal çıkışına sahip olma arzusu ve uzun çalışan bir sunucuya sahip olma arzusu bunu önemsiz hale getirmez.
İstemciden RPC çağrısı geldikten hemen sonra, kendilerine yazdırılan verileri istemciye yönlendiren iki RpcOutputStream
örneği oluşturulur (stdout ve stderr için). Bunlar daha sonra bir OutErr
(stdout, stderr) çifti içinde sarmalanır. Konsolda yazdırılması gereken her şey bu akışlardan geçer. Ardından bu akışlar BlazeCommandDispatcher.execExclusively()
adlı ağa aktarılır.
Çıktı varsayılan olarak ANSI kod dışına alma sıralarıyla yazdırılır. Bunlar istenmediğinde (--color=no
) bir 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 yine de istemcinin terminal çıkışına (sunucununkinden farklıdır) gönderilir. Bir süreç ikili çıkış (bazel query --output=proto
gibi) üretiyorsa stdout'un düzeltilmemesine dikkat edilir.
Kısa mesajlar (hatalar, uyarılar vb.), EventHandler
arayüzü üzerinden ifade edilir. Özellikle, bunlar EventBus
platformuna yayınlananlardan farklıdır (bu durum kafa karıştırıcıdır). Her Event
bir EventKind
(hata, uyarı, bilgi ve birkaç tane daha) içerir ve Location
(kaynak kodunda etkinliğin gerçekleşmesine neden olan yer) olabilir.
Bazı EventHandler
uygulamaları, aldıkları etkinlikleri depolar. Bu, önbelleğe alınmış yapılandırılmış bir hedefin yayınladığı uyarılar gibi çeşitli önbelleğe alınmış işlemlerin neden olduğu bilgileri kullanıcı arayüzünde tekrar oynatmak için kullanılır.
Bazı EventHandler
'ler, sonunda etkinlik otobüsüne giden etkinliklerin yayınlanmasına da izin verir (normal Event
'ler orada _görünmez). Bunlar ExtendedEventHandler
uygulamalarıdır ve ana kullanımları, önbelleğe alınan EventBus
etkinliklerini tekrar oynatmaktır. Bu EventBus
etkinliklerinin tümü Postable
işlemini uygular, ancak EventBus
sayfasında yayınlanan her şey bu arayüzü zorunlu kılmaz, yalnızca ExtendedEventHandler
tarafından önbelleğe alınanlar (Güzel olur ve çoğu şey işe yarar, ancak zorunlu kılınmaz)
Terminal çıkışı çoğunlukla UiEventHandler
üzerinden yayınlanır. Bu, Bazel'ın yaptığı tüm süslü çıkış biçimlendirme ve ilerleme raporlama işlemlerinden sorumludur. İki girişi vardır:
- Etkinlik otobüsü
- Etkinlik akışı Bildiren kişi aracılığıyla kanala aktarılır
Komut yürütme makinesinin (örneğin, Bazel'ın geri kalanı) RPC akışıyla istemciye yönelik tek doğrudan bağlantısı, bu akışlara doğrudan erişim sağlayan Reporter.getOutErr()
üzerindendir. Yalnızca bir komutun büyük miktarda olası ikili verinin (bazel query
gibi) dökümünü alması gerektiğinde kullanılır.
Bazel Profil Oluşturma
Bazel hızlı. Ayrıca Bazel yavaştır, çünkü yapılamalar dayanılması gereken bir sınıra kadar büyüme eğilimindedir. Bu nedenle Bazel, profil oluşturmaları ve Bazel'ın kendisi için kullanılabilecek bir profilci içerir. Bu, Profiler
olarak adlandırılmış bir sınıfta uygulanır. Varsayılan olarak açıktır. Ancak, ek yüküne izin verilmeyeceği şekilde yalnızca kısaltılmış verileri kaydeder. Komut satırı --record_full_profiler_data
, mümkün olan her şeyi kaydetmesini sağlar.
Chrome profil aracı biçiminde bir profil oluşturur. En iyi şekilde Chrome'da görüntülenir. Veri modeli, görev yığınlarıdır: Bir kişi görevlere başlayabilecek ve görevleri bitirebilir ve bunların düzenli olarak iç içe yerleştirilmiş olması gerekir. Her Java iş parçacığının kendi görev yığını vardır. TODO: Bu, işlemler ve devamlı geçiş stiliyle nasıl çalışır?
Profil oluşturucu sırasıyla BlazeRuntime.initProfiler()
ve BlazeRuntime.afterCommand()
bölgelerinde başlatılıp durdurulur ve her şeyin profilini çıkarabilmemiz 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. Kapatılması görevin sonunu temsil eden bir Closeable
döndürür. Kaynakları deneyin ifadeleriyle
kullanılması önerilir.
MemoryProfiler
ürününde de temel bellek profili oluşturma işlemi yaparız. Ayrıca bu özellik her zaman açıktır ve çoğunlukla maksimum yığın boyutlarını ve GC davranışını kaydeder.
Test Bazelleri
Bazel temel olarak iki tür teste sahiptir: Bazel'i "siyah kutu" olarak gözlemleyenler ve yalnızca analiz aşamasını yürüten testler. İlklerine "entegrasyon testleri" ve ikincilerine "birim testleri" diyoruz. Ancak bunlar daha az entegre olan entegrasyon testlerine benzer. Ayrıca, gerekli olan bazı gerçek birim testlerimiz de vardır.
Entegrasyon testlerinde iki tür test bulunur:
src/test/shell
altında, çok ayrıntılı bir bash testi çerçevesi kullanılarak uygulanan uygulamalar- Java'da uygulananlar. Bunlar
BuildIntegrationTestCase
alt sınıfları olarak uygulanır
Çoğu test senaryosu için uygun donanıma sahip olduğundan, BuildIntegrationTestCase
tercih edilen entegrasyon testi çerçevesidir. Java çerçevesi olduğundan hata ayıklaması yapılabilirlik 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 bulabilirsiniz.
Analiz testleri, BuildViewTestCase
alt sınıfları olarak uygulanır. BUILD
dosyalarını yazmak için kullanabileceğiniz bir ödül dosya sistemi vardır ve 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 şeyler iddia edebilir.