Como a Microsoft sincroniza o Virtual Monorepo (VMR) do .NET

O virtual monorepo é a estratégia que a Microsoft criou para construir o SDK do .NET a partir de um único repositório, sem abandonar os repositórios separados de cada componente. À medida que um produto cresce em componentes, equipes e frequência de mudanças, a organização do código deixa de ser detalhe operacional e passa a pesar no custo de engenharia.

O ecossistema do .NET mostra bem esse cenário, porque envolve dezenas de repositórios, equipes diferentes e um pipeline de build sofisticado. Tradicionalmente, um produto assim segue um de dois caminhos: centralizar tudo em um monorepo ou manter repositórios separados.

Cada abordagem traz vantagens e problemas. Por isso, a Microsoft construiu uma terceira alternativa, chamada Virtual Monolithic Repository (VMR), que tenta capturar o melhor dos dois mundos.

O que é o virtual monorepo do .NET

O virtual monorepo é um repositório Git que oferece uma visão unificada do produto. Ele reúne o código e a infraestrutura necessários para construir o SDK do .NET a partir de um único commit, e a Microsoft o publica como dotnet/dotnet no GitHub.

A característica principal do VMR é que ele não substitui os repositórios originais. Em vez disso, ele os agrega em subdiretórios, enquanto os repositórios de origem continuam vivos e relevantes.

A palavra "virtual" é crucial aqui. Na prática, o virtual monorepo se comporta como um monorepo para build e integração, mas continua refletindo um conjunto de repositórios independentes.

De forma conceitual, o VMR funciona como um repositório de integração com quatro papéis:

  • Consolidar o código de múltiplos repositórios em um único local.
  • Habilitar builds completos e consistentes do SDK.
  • Facilitar a validação de mudanças que afetam mais de um componente.
  • Oferecer uma visão agregada do produto para equipes e automações.

O vídeo abaixo complementa a explicação com uma visão geral do tema.

Por que um monorepo pode ser difícil no .NET

O SDK do .NET não é um conjunto único e coeso de arquivos. Na prática, ele integra componentes como runtime, SDK, ASP.NET Core, Roslyn e diversas ferramentas, que evoluem em paralelo.

Em um modelo com vários repositórios, cada um tem ciclo de vida, pipeline de CI/CD, versionamento, issues e pull requests próprios. Essa independência aumenta a autonomia das equipes, mas cria uma consequência inevitável: o produto final é sempre a soma de partes que precisam ser coordenadas.

Essa coordenação costuma introduzir fricções como estas:

  • Dificuldade para reproduzir localmente builds complexos.
  • Atrasos ou inconsistências na propagação de mudanças entre componentes.
  • Dependências implícitas entre pipelines e artefatos.
  • Pouca visibilidade sobre o estado agregado do produto.
  • Complexidade para testar alterações que cruzam fronteiras de repositório.

Por outro lado, migrar tudo para um monorepo tradicional seria disruptivo. Afinal, equipes, processos e ferramentas foram construídos ao longo de anos em torno dos repositórios separados.

Assim, a pressão por builds mais previsíveis e eficientes exigia uma evolução estrutural que não quebrasse o modelo existente. O virtual monorepo nasceu exatamente dessa tensão.

Como o virtual monorepo está organizado

No VMR, os repositórios de produto, como dotnet/runtime e dotnet/sdk, aparecem como pastas dentro do diretório src/. Além disso, a pasta eng/ guarda a infraestrutura de build e a pasta docs/ reúne a documentação.

Do ponto de vista do desenvolvedor, isso significa clonar um único repositório e construir o SDK com uma visão completa do produto. Esse modelo ajuda muito quando uma alteração depende de mudanças coordenadas em vários componentes.

Sem o VMR, a sequência tradicional seria mais ou menos esta:

  1. Abrir um pull request no repositório A.
  2. Esperar o merge.
  3. Atualizar as dependências no repositório B.
  4. Abrir outro pull request no repositório B.
  5. Repetir o ciclo nos demais repositórios afetados.

Com o virtual monorepo, o conjunto de alterações pode ser analisado de forma integrada, em um único lugar.

Arquivos de metadados do virtual monorepo

Para saber o que já foi sincronizado, o virtual monorepo depende de alguns arquivos de controle. O primeiro é o source-manifest.json, que registra o SHA do commit de cada repositório de produto sincronizado e dos submódulos incorporados.

Do outro lado, cada repositório de produto guarda no Version.Details.xml o SHA do último commit do VMR que recebeu. Com isso, a automação consegue calcular exatamente quais mudanças faltam em cada direção.

Também existe o source-mappings.json, que define os mapeamentos entre repositórios e pastas. Ele permite, por exemplo, excluir arquivos binários de testes e copiar conteúdo adicional para outros caminhos do VMR.

Os submódulos Git recebem um tratamento especial. Em vez de manter referências de submódulo, que atrapalham builds offline, o VMR traz o código desses submódulos como cópia física e remove os binários durante o processo.

A evolução do virtual monorepo até o .NET 10

O VMR não surgiu pronto. Na verdade, ele passou por três estágios, cada um resolvendo um problema concreto da época.

Source build e os tarballs

A história começou com as distribuições Linux, que exigem builds feitos apenas a partir do código-fonte e sem acesso à internet. Para atender esse requisito, o processo de Source Build percorria os commits dos repositórios e gerava tarballs com todo o código.

Quando a integração quebrava, a equipe aplicava os chamados Source Build patches. Esses patches corrigiam o problema até que a correção definitiva chegasse pela árvore de dependências.

VMR-lite, o espelho somente leitura

Em outubro de 2022, a Microsoft lançou a primeira versão do dotnet/dotnet como um espelho somente leitura. Nesse modelo, um pipeline de sincronização em mão única disparava quando um commit chegava ao repositório do SDK.

Esse VMR-lite virou o veículo de release do .NET 8 Preview 1 para linux-x64. Além disso, ele continua atendendo o servicing do .NET 8 e do .NET 9.

VMR com escrita a partir do .NET 10

No .NET 10 Preview 4, a Microsoft passou a construir o SDK completo a partir de um único commit do monorepo. Em seguida, no Preview 5, o fluxo de dependências em árvore deu lugar a uma sincronização direta e bidirecional entre repositórios de produto e VMR.

A partir desse ponto, o repositório deixou de ser apenas um espelho. Desenvolvedores também podem alterar código diretamente no VMR, e essas mudanças voltam para os repositórios de origem.

Sincronização bidirecional: forward flow e backflow

O que torna o virtual monorepo tecnicamente diferenciado é a sincronização bidirecional. Em outras palavras, as mudanças não fluem apenas da origem para o monorepo, mas também do monorepo para a origem.

A Microsoft usa uma terminologia própria para essas direções:

  • Forward flow: mudanças que saem de um repositório de produto e chegam ao VMR.
  • Backflow: mudanças que saem do VMR e voltam para um repositório de produto.
  • Code flow: o nome genérico para o movimento de código nas duas direções.

Esse desenho preserva a flexibilidade de contribuição. Algumas mudanças continuam nascendo nos repositórios originais, porque esse é o fluxo natural das equipes.

Já as mudanças que atravessam vários componentes podem nascer diretamente no VMR. Dessa forma, a sincronização bidirecional mantém as duas visões consistentes e evita divergências prolongadas.

Como a Microsoft sincroniza o virtual monorepo na prática

A sincronização do virtual monorepo depende do serviço de fluxo de dependências do arcade-services. Esse serviço calcula as diferenças e, em vez de enviar commits direto para o destino, abre pull requests de um lado para o outro.

O fluxo conceitual funciona assim:

  1. Uma equipe faz uma mudança em um repositório de produto, como uma correção no runtime.
  2. Quando o build oficial é promovido, a automação abre um pull request de forward flow no VMR.
  3. Alternativamente, alguém altera vários componentes de uma vez diretamente no VMR.
  4. A automação identifica quais pastas pertencem a quais repositórios de produto.
  5. Em seguida, ela abre pull requests de backflow, junto com os pacotes atualizados, nos repositórios correspondentes.

Na prática, o VMR passa a ser um hub de integração e uma visão canônica do build do SDK. Também se torna um mecanismo que reduz o custo das mudanças transversais.

Patches Git como unidade de transporte

Por baixo dos panos, as mudanças viajam como patches Git. Se você quer entender melhor esses objetos internos, vale ler o artigo sobre o Git por dentro do encanamento.

A automação gera e aplica os patches com comandos parecidos com estes:

# Gera o patch entre o último commit sincronizado e o novo commit
git diff --patch --binary --relative <last-synced-sha> <new-sha> > changes.patch

# Aplica o patch no índice do repositório de destino
git apply --cached --ignore-space-change changes.patch

Como o Git tem um limite próximo de 1 GB para patches, a ferramenta divide recursivamente os patches muito grandes. Depois disso, ela atualiza o source-manifest.json com os novos SHAs.

No modelo antigo de mão única, o processo ainda revertia os Source Build patches antes da sincronização. Ao final, ele os reaplicava, junto com o conteúdo mapeado adicionalmente.

Code flow v2 e o rebase interativo

A primeira versão do code flow tinha limitações sérias com conflitos. Por isso, a Microsoft criou o code flow v2, baseado em rebase, e o colocou em produção em dezembro de 2025.

O algoritmo atual segue estas etapas:

  1. Calcula as mudanças considerando os fluxos anteriores nas duas direções.
  2. Tenta fazer rebase do branch sobre a ponta do branch de destino.
  3. Se tudo der certo, o merge acontece automaticamente.
  4. Se houver conflito, o serviço abre um pull request vazio com instruções para resolução local.
  5. Ferramentas customizadas resolvem os conflitos conhecidos, como os de manifesto.
  6. O desenvolvedor resolve os conflitos reais e envia o resultado.
  7. Por fim, o serviço valida o pull request e libera o merge.

Assim, o rebase interativo resolve automaticamente o que é previsível e deixa para humanos apenas os conflitos genuínos.

Conflitos e desafios do virtual monorepo

Mesmo com o code flow v2, o virtual monorepo ainda enfrenta problemas difíceis. O primeiro deles é o das mudanças graduais.

Quando alguém altera um arquivo aos poucos em uma direção, fluxos paralelos podem gerar falsos conflitos. Isso acontece mesmo quando as mudanças, de fato, não conflitam entre si.

O segundo é o problema do revert. Se uma mudança entra e depois é revertida em momentos diferentes, um rebase forçado para commits antigos pode perder o revert inteiro.

Além disso, os branches dos produtos seguem modelos de ciclo de vida diferentes. O dotnet/sdk ramifica por bandas de SDK, enquanto dotnet/runtime e dotnet/aspnetcore seguem outro ritmo, e o dotnet/roslyn acompanha o Visual Studio.

Quando um repositório cria o branch de release antes do VMR, os commits podem perder a relação de pai e filho. Por esse motivo, a Microsoft agora conduz esses cortes de branch de forma centralizada, a partir do VMR.

Por fim, merges entre branches de release, como de release/10.0.1xx para release/10.0.2xx, podem sobrescrever os metadados de rastreamento. A equipe estuda usar Git notes para guardar esses dados fora dos arquivos versionados.

Como compilar o SDK a partir do virtual monorepo

Uma vantagem concreta do virtual monorepo é que qualquer pessoa consegue reproduzir o build do SDK localmente. O README do repositório documenta os comandos principais:

# Linux e macOS
./build.sh --clean-while-building

# Windows
.\build.cmd -cleanWhileBuilding

# Build somente a partir do código-fonte (cenário das distribuições Linux)
./prep-source-build.sh
./build.sh -sb

O suporte varia conforme a versão. No .NET 8 e no .NET 9, o cenário suportado é o source build em Linux, enquanto a partir do .NET 10 o build também funciona em Mac e Windows.

Esse recurso ajuda clientes a testarem correções de bugs localmente. Se você quer investigar builds com mais profundidade, o artigo sobre o Binlog MCP Server para diagnóstico de builds mostra outra ferramenta útil.

Quanto às contribuições, a recomendação oficial é clara. Mudanças em src/* devem nascer nos repositórios de produto, e somente mudanças de infraestrutura ou transversais devem ir direto para o VMR.

Por que isso importa para build e consistência do produto

Em um ecossistema com vários repositórios, o build costuma depender de artefatos e cadeias de pacotes entre pipelines. Ou seja, o estado real do produto fica mais bem representado por um conjunto de versões intermediárias do que pelo próprio código-fonte.

Ao consolidar a integração no virtual monorepo, a Microsoft consegue:

  • Construir o produto de maneira mais determinística.
  • Validar mudanças considerando o SDK como um todo.
  • Reduzir a dependência de artefatos intermediários como única forma de integração.
  • Registrar o estado agregado do SDK em um único ponto.

Para a engenharia, o modelo também melhora a reprodutibilidade, o diagnóstico de falhas, a rastreabilidade das mudanças e a consistência do build entre ambientes.

Na colaboração, o ganho é semelhante. O desenvolvedor implementa e valida mudanças integradas, enquanto a automação decompõe e propaga as alterações para os repositórios corretos.

Trade-offs e lições para engenharia em larga escala

Apesar das vantagens claras, o VMR não sai de graça. Ele exige automações robustas de sincronização e tratamento cuidadoso de conflitos entre alterações concorrentes.

Também exige governança clara sobre quando mudar no repositório de produto e quando mudar no VMR. Além disso, o custo de armazenamento e de clone tende a crescer em cenários internos.

Existe ainda um aspecto cultural. Equipes que sempre trabalharam em repositórios independentes precisam alinhar práticas para que o VMR não vire um ponto de tensão organizacional.

O lado positivo é que, ao manter os repositórios originais vivos, a Microsoft evita uma migração disruptiva e permite uma adoção gradual. Essa escolha mostra uma lição importante: a forma como o código é organizado é uma decisão arquitetural, não apenas um detalhe de repositório.

O caso do virtual monorepo responde a necessidades bem concretas: produto grande, muitas equipes, dependências complexas e mudanças que cruzam fronteiras com frequência. Em vez de escolher um extremo, a arquitetura busca um caminho intermediário que melhora o sistema sem reescrever o passado.

Conclusão

O virtual monorepo do .NET concilia autonomia e integração em larga escala. Ele oferece uma experiência de monorepo para construir e validar o SDK, enquanto preserva o ecossistema de repositórios existente.

A sincronização bidirecional, baseada em patches Git, pull requests automáticos e no code flow v2, permite que mudanças nasçam tanto nos repositórios de produto quanto no VMR. Com isso, as duas visões permanecem consistentes.

Mais do que um detalhe de infraestrutura, o virtual monorepo mostra como a engenharia de plataforma pode transformar a produtividade das equipes. Portanto, vale estudar o modelo mesmo que seu produto seja bem menor que o .NET.

FAQ

O que significa VMR no .NET?

VMR significa Virtual Monolithic Repository. É o repositório dotnet/dotnet, que agrega o código dos repositórios de produto para construir o SDK a partir de um único commit.

O VMR substitui os repositórios como dotnet/runtime?

Não. Ele espelha os repositórios de produto, que continuam sendo o lugar principal para a maior parte das contribuições.

O que é forward flow e backflow?

Forward flow é o movimento de mudanças de um repositório de produto para o VMR. Backflow é o caminho inverso, do VMR de volta para o repositório de produto.

Desde quando o VMR aceita mudanças diretas?

A sincronização bidirecional entrou em operação no .NET 10 Preview 5. Antes disso, o VMR-lite funcionava apenas como espelho somente leitura.

Posso compilar o SDK do .NET a partir do VMR?

Sim. Basta clonar o dotnet/dotnet e executar build.sh ou build.cmd, conforme a plataforma e a versão suportada.

Glossário

Termo Significado
Monorepo Repositório único que contém o código de vários projetos ou componentes.
Multi-repo Modelo em que cada componente vive em um repositório próprio.
VMR Virtual Monolithic Repository, o repositório dotnet/dotnet que agrega os repositórios do .NET.
Forward flow Fluxo de mudanças de um repositório de produto para o VMR.
Backflow Fluxo de mudanças do VMR de volta para um repositório de produto.
Code flow Movimento de código entre VMR e repositórios de produto, nas duas direções.
Source build Build feito apenas a partir do código-fonte, sem acesso à internet, exigido por distribuições Linux.
Patch Arquivo que descreve as diferenças entre dois estados do código e pode ser aplicado em outro repositório.
Rebase Operação do Git que reaplica commits sobre outra base.
Servicing Ciclo de correções e atualizações de uma versão já lançada.

Fontes