C# Publicar o Programa
O relatório da turma está pronto, testado e funcionando no computador da Ana. Agora a coordenadora da escola quer usá-lo no computador dela, que não tem o SDK do .NET, não tem o código e não sabe o que é dotnet run. Publicar é preparar o programa para isso: compilar a versão final e juntar numa pasta tudo o que ele precisa para rodar em outra máquina. Nesta aula você vai publicar o mesmo programa de cinco jeitos e comparar os tamanhos reais, de 136 KB a 80 MB.
- A usar
dotnet publishe a ler o que vai na pastapublish. - A diferença entre um programa dependente do .NET e um autocontido (
--self-contained), e o que é o-r linux-x64. - Um executável só (
PublishSingleFile), menor (PublishTrimmed) ou em código nativo (PublishAot). - A publicar para o Windows (
-r win-x64) a partir do Linux. - A publicar um arquivo solto (
dotnet publish arquivo.cs) .NET 10. - Os erros
NETSDK1083e "You must install or update .NET".
Todas as saídas e todos os tamanhos desta aula são reais, feitos com o .NET 10 SDK (10.0.112) na máquina fictícia da Ana, no programa Escola.Relatorio da aula Bibliotecas de Classes (ele usa a biblioteca Escola.Notas). Os tamanhos mudam um pouco de versão para versão do .NET. Algumas formas baixam pacotes do NuGet na primeira vez e precisam de internet.
A ideia: a marmita e o restaurante
A Ana quer levar o almoço para a coordenadora. Ela tem duas opções:
- Só a comida, numa marmita pequena. Leve e prática, mas a coordenadora precisa ter prato, talheres e micro-ondas em casa.
- A comida com prato, talheres e um micro-ondas portátil. Funciona em qualquer lugar, mas a sacola fica enorme.
Publicar um programa é escolher entre essas duas sacolas. A "comida" é o seu código compilado (alguns KB). O "micro-ondas" é o runtime do .NET, o motor que roda programas .NET (dezenas de MB, veja .NET: SDK, Runtime e CLR).
| Forma | O que vai junto | A outra máquina precisa de |
|---|---|---|
| Dependente do .NET (o padrão) | Só o seu código | O runtime do .NET 10 instalado |
| Autocontido (self-contained) | O seu código + o runtime inteiro | Nada: só o sistema certo (Linux x64, Windows x64…) |
1. dotnet publish
$ dotnet publish Determinando os projetos a serem restaurados... Todos os projetos estão atualizados para restauração. Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Release/net10.0/Escola.Notas.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/Escola.Relatorio.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/publish/
- dotnet publish
- "Publique": compila e junta o programa numa pasta pronta para levar. Desde o .NET 8, o
publishusa a configuração Release por padrão (repare nobin/Release): otimizada, semDebug.WriteLine(veja Depuração). - Escola.Notas -> …
- A biblioteca que o programa usa foi compilada também, em Release.
- … -> …/bin/Release/net10.0/publish/
- A pasta final. É ela que você copia para a outra máquina.
$ ls -l bin/Release/net10.0/publish/ total 132 -rw-rw-r-- 1 ana ana 5120 out 17 13:27 Escola.Notas.dll -rw-rw-r-- 1 ana ana 11140 out 17 13:27 Escola.Notas.pdb -rw-rw-r-- 1 ana ana 1257 out 17 13:27 Escola.Notas.xml -rwxr-xr-x 1 ana ana 78904 out 17 13:27 Escola.Relatorio -rw-rw-r-- 1 ana ana 791 out 17 13:27 Escola.Relatorio.deps.json -rw-rw-r-- 1 ana ana 6144 out 17 13:27 Escola.Relatorio.dll -rw-rw-r-- 1 ana ana 11080 out 17 13:27 Escola.Relatorio.pdb -rw-rw-r-- 1 ana ana 330 out 17 13:27 Escola.Relatorio.runtimeconfig.json $ du -sh bin/Release/net10.0/publish/ 136K bin/Release/net10.0/publish/ $ ./bin/Release/net10.0/publish/Escola.Relatorio Ana 7,9 Aprovado Beto 6,3 Recuperação
- Escola.Relatorio.dll (6 KB) e Escola.Notas.dll (5 KB)
- O código da Ana, compilado. É só isso: 11 KB.
- Escola.Relatorio (77 KB, com
xde executável) - O lançador (em inglês, apphost): um executável pequeno do Linux que procura o runtime do .NET instalado e manda ele rodar o
.dll. É o que a coordenadora roda com./Escola.Relatorio. Sem ele, daria para rodar comdotnet Escola.Relatorio.dll. - .runtimeconfig.json
- Diz qual runtime o programa quer (veja logo abaixo).
- .deps.json
- A lista das dependências (aqui, a
Escola.Notas). - .pdb e .xml
- As informações de depuração (para o stack trace mostrar as linhas) e a documentação da biblioteca. O programa roda sem eles; os
.pdbvalem a pena manter, porque deixam os erros mais fáceis de entender. - 136K
- A pasta inteira tem 136 KB: cabe em qualquer lugar.
$ cat bin/Release/net10.0/publish/Escola.Relatorio.runtimeconfig.json
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
"Preciso do Microsoft.NETCore.App (o runtime de console), versão 10.0.0 ou mais nova do .NET 10." Essa é a "marmita pequena": o computador da coordenadora precisa ter o runtime do .NET 10 instalado (no Ubuntu, sudo apt install dotnet-runtime-10.0; veja Instalar no Linux). Para levar, a Ana empacota a pasta:
$ cd bin/Release/net10.0 && tar -czf ~/relatorio.tar.gz publish && cd - > /dev/null $ ls -l ~/relatorio.tar.gz -rw-rw-r-- 1 ana ana 44192 out 17 13:28 /home/ana/relatorio.tar.gz
43 KB compactados. A coordenadora descompacta com tar -xzf relatorio.tar.gz e roda ./publish/Escola.Relatorio.
2. Autocontido: -r linux-x64 --self-contained
E se a coordenadora não tiver (nem puder instalar) o .NET? Aí o runtime vai junto:
$ dotnet publish -r linux-x64 --self-contained Determinando os projetos a serem restaurados... /home/ana/projetos/Escola/src/Escola.Relatorio/Escola.Relatorio.csproj restaurado (em 3,4 seg). /home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj restaurado (em 3,38 seg). Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Release/net10.0/Escola.Notas.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/linux-x64/Escola.Relatorio.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/linux-x64/publish/ $ ls bin/Release/net10.0/linux-x64/publish/ | wc -l 195 $ ls bin/Release/net10.0/linux-x64/publish/ | grep -c '^System\.' 164 $ ls -l bin/Release/net10.0/linux-x64/publish/libcoreclr.so bin/Release/net10.0/linux-x64/publish/System.Private.CoreLib.dll -rwxrw-r-- 1 ana ana 7110952 ago 22 11:26 bin/Release/net10.0/linux-x64/publish/libcoreclr.so -rwxrw-r-- 1 ana ana 15576872 ago 22 11:31 bin/Release/net10.0/linux-x64/publish/System.Private.CoreLib.dll $ du -sh bin/Release/net10.0/linux-x64/publish/ 80M bin/Release/net10.0/linux-x64/publish/ $ ./bin/Release/net10.0/linux-x64/publish/Escola.Relatorio Ana 7,9 Aprovado Beto 6,3 Recuperação
- -r linux-x64
- O RID (Runtime Identifier, "identificador do ambiente"): para que sistema e processador publicar.
linux-x64= Linux em processador Intel/AMD de 64 bits. Um programa autocontido leva um runtime feito para um sistema só, por isso o RID é obrigatório. - --self-contained
- "Autocontido": leva o runtime junto.
- restaurado (em 3,4 seg)
- Na primeira vez, o NuGet baixou o runtime para
linux-x64(o pacoteMicrosoft.NETCore.App.Runtime.linux-x64, veja Pacotes NuGet). - 195 arquivos, 164 deles
System.* - A biblioteca inteira do .NET:
System.Collections.dll,System.Linq.dll,System.Text.Json.dll… Mesmo as que o programa nunca usa. - libcoreclr.so (6,8 MB)
- O motor que roda o código (o CLR). A data de agosto é a da compilação do runtime pela Microsoft, não a da Ana.
- System.Private.CoreLib.dll (15 MB)
- O coração da biblioteca do .NET:
string,int,List,Console. - 80M
- De 136 KB para 80 MB: o preço de não depender de nada instalado.
Os RIDs mais comuns
| RID | Sistema |
|---|---|
linux-x64 | Linux, Intel/AMD 64 bits (a maioria dos PCs e servidores) |
linux-arm64 | Linux em ARM 64 bits (Raspberry Pi 4 e 5, alguns servidores) |
linux-musl-x64 | Linux com a biblioteca musl (Alpine, comum em contêineres) |
win-x64 | Windows, Intel/AMD 64 bits |
osx-arm64 | macOS em Mac com chip Apple (M1, M2…) |
3. Menos arquivos e menos megabytes
Um arquivo só: PublishSingleFile
195 arquivos são incômodos de copiar. A propriedade PublishSingleFile junta tudo num executável. As propriedades do .csproj podem ir na linha de comando com -p:Nome=valor (veja O Arquivo .csproj), e o -o escolhe a pasta de saída:
$ dotnet publish -r linux-x64 --self-contained -p:PublishSingleFile=true -o saida/arquivo-unico «…» Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/saida/arquivo-unico/ $ ls -l saida/arquivo-unico total 71904 -rw-rw-r-- 1 ana ana 11140 out 17 13:27 Escola.Notas.pdb -rw-rw-r-- 1 ana ana 1257 out 17 13:27 Escola.Notas.xml -rwxr-xr-x 1 ana ana 73599992 out 17 13:27 Escola.Relatorio -rw-rw-r-- 1 ana ana 11092 out 17 13:27 Escola.Relatorio.pdb $ ./saida/arquivo-unico/Escola.Relatorio Ana 7,9 Aprovado Beto 6,3 Recuperação
Um executável de 70 MB (contra os 80 MB da pasta), mais os .pdb e o .xml, que são opcionais.
Tirar o que não é usado: PublishTrimmed
O programa da Ana usa uma fração minúscula das 164 bibliotecas System.*. O PublishTrimmed ("publicar aparado") analisa o programa e corta o que ele nunca chama:
$ dotnet publish -r linux-x64 --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -o saida/aparado Determinando os projetos a serem restaurados... Todos os projetos estão atualizados para restauração. Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Release/net10.0/Escola.Notas.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/linux-x64/Escola.Relatorio.dll Otimizando montagens para tamanho. Esse processo pode demorar um pouco. Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/saida/aparado/ $ ls -l saida/aparado total 14640 -rw-rw-r-- 1 ana ana 11128 out 17 13:28 Escola.Notas.pdb -rw-rw-r-- 1 ana ana 1257 out 17 13:27 Escola.Notas.xml -rwxr-xr-x 1 ana ana 14960014 out 17 13:28 Escola.Relatorio -rw-rw-r-- 1 ana ana 11108 out 17 13:28 Escola.Relatorio.pdb $ ./saida/aparado/Escola.Relatorio Ana 7,9 Aprovado Beto 6,3 Recuperação
"Otimizando montagens (assemblies) para tamanho": de 70 MB para 14 MB, ainda autocontido. O cuidado: o corte é feito por análise do código, e um programa que usa Reflexão (procurar classes e métodos pelo nome, em tempo de execução) pode precisar de algo que foi cortado. O compilador avisa nesses casos com avisos IL2026 e parecidos. Programas pequenos como o da Ana não têm esse problema; sempre teste o executável aparado antes de entregar.
Código nativo: PublishAot
Nas formas anteriores, o executável ainda leva o runtime, que traduz o código do .NET para a linguagem do processador enquanto o programa roda (é o "JIT", veja .NET). Com PublishAot (de Ahead-Of-Time, "antes da hora"), essa tradução é feita na publicação, e o resultado é um executável nativo do Linux, como um programa em C:
$ dotnet publish -r linux-x64 -p:PublishAot=true -o saida/nativo Determinando os projetos a serem restaurados... /home/ana/projetos/Escola/src/Escola.Notas/Escola.Notas.csproj restaurado (em 2,62 seg). /home/ana/projetos/Escola/src/Escola.Relatorio/Escola.Relatorio.csproj restaurado (em 2,62 seg). Escola.Notas -> /home/ana/projetos/Escola/src/Escola.Notas/bin/Release/net10.0/Escola.Notas.dll Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/bin/Release/net10.0/linux-x64/Escola.Relatorio.dll Generating native code Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/saida/nativo/ $ ls -l saida/nativo total 4468 -rw-rw-r-- 1 ana ana 11140 out 17 13:28 Escola.Notas.pdb -rw-rw-r-- 1 ana ana 1257 out 17 13:28 Escola.Notas.xml -rwxrwxr-x 1 ana ana 1399496 out 17 13:28 Escola.Relatorio -rwxrwxr-x 1 ana ana 3154512 out 17 13:28 Escola.Relatorio.dbg $ ./saida/nativo/Escola.Relatorio Ana 7,9 Aprovado Beto 6,3 Recuperação
- Generating native code
- "Gerando código nativo": o compilador AOT (o ILCompiler, baixado do NuGet na primeira vez) traduziu tudo para instruções do processador. No Linux, ele usa o
clangpara montar o executável: instale comsudo apt install clangse não tiver. - Escola.Relatorio (1,3 MB)
- Um executável nativo, autocontido, sem nenhum
.dllao lado. O--self-containednem precisou ser escrito: com AOT, é sempre assim. - Escola.Relatorio.dbg
- As informações de depuração do código nativo, num arquivo separado. Não vai para a outra máquina.
Além de pequeno, o executável nativo começa a rodar muito mais rápido, porque não tem runtime para iniciar nem código para traduzir:
$ time ./bin/Release/net10.0/publish/Escola.Relatorio > /dev/null real 0m0,052s user 0m0,040s sys 0m0,012s $ time ./saida/nativo/Escola.Relatorio > /dev/null real 0m0,005s user 0m0,000s sys 0m0,005s
52 milissegundos contra 5 (o time mede quanto o comando levou; o real é o tempo de relógio). Para um programa que roda e termina, como um comando de terminal, faz diferença. As restrições do AOT são as do PublishTrimmed e mais algumas: a reflexão é limitada, e nada de carregar código novo em tempo de execução.
4. Publicar para o Windows, a partir do Linux
A secretaria da escola usa Windows. Não precisa de um Windows para publicar: basta trocar o RID.
$ dotnet publish -r win-x64 --self-contained -o saida/windows 2>&1 | tail -1 Escola.Relatorio -> /home/ana/projetos/Escola/src/Escola.Relatorio/saida/windows/ $ ls saida/windows | grep -i escola Escola.Notas.dll Escola.Notas.pdb Escola.Notas.xml Escola.Relatorio.deps.json Escola.Relatorio.dll Escola.Relatorio.exe Escola.Relatorio.pdb Escola.Relatorio.runtimeconfig.json
Lá está o Escola.Relatorio.exe, o lançador do Windows, junto com o runtime do Windows. A Ana não consegue rodá-lo no Linux, mas pode copiar a pasta para o computador da secretaria. (O PublishAot é a exceção: o código nativo só pode ser gerado no próprio sistema de destino.)
5. Publicar um arquivo solto .NET 10
Um arquivo rodado com dotnet run arquivo.cs também pode ser publicado. A Ana usou um programinha que diz em que configuração foi compilado:
#if DEBUG
Console.WriteLine("Modo: Debug");
#else
Console.WriteLine("Modo: Release");
#endif
$ dotnet run modo.cs Modo: Debug $ dotnet publish modo.cs Determinando os projetos a serem restaurados... /home/ana/cs/publicar/modo.csproj restaurado (em 369 ms). modo -> /home/ana/.local/share/dotnet/runfile/modo-46178f36cfe0fabfe574a798d3444c07f827d06d6620a62dba15a6082a464f2e/bin/release/modo.dll Generating native code modo -> /home/ana/cs/publicar/artifacts/modo/ $ ls -l artifacts/modo total 4032 -rwxrwxr-x 1 ana ana 1270776 out 17 13:29 modo -rwxrwxr-x 1 ana ana 2854720 out 17 13:29 modo.dbg $ ./artifacts/modo/modo Modo: Release
- ~/.local/share/dotnet/runfile/modo-…
- O projeto "escondido" que o
dotnetcria para um arquivo solto (veja O Arquivo .csproj). O código compilado fica lá. - Generating native code
- Para arquivos soltos, o padrão é AOT (é o
<PublishAot>true</PublishAot>que odotnet project convertmostrou). - artifacts/modo/modo
- O executável nativo, de 1,2 MB, numa pasta
artifacts("artefatos", os produtos da compilação) ao lado do.cs. - Modo: Release
- O
dotnet runcompila em Debug; odotnet publish, em Release. Por isso a mesma linha mostra coisas diferentes.
No editor do guia, o programa é compilado como no dotnet run: em Debug. Experimente:
Missão: o programa abaixo sempre diz Modo: ?. Troque a linha por um bloco #if DEBUG … #else … #endif que mostre Modo: Debug numa configuração e Modo: Release na outra, como o modo.cs. Aqui, a saída deve ser Modo: Debug; publicado, o mesmo código diria Modo: Release.
Console.WriteLine("Modo: ?");
#if DEBUG
Console.WriteLine("Modo: Debug");
#else
Console.WriteLine("Modo: Release");
#endif
Qual forma usar?
| Comando | Tamanho (o relatório da Ana) | Quando usar |
|---|---|---|
dotnet publish | 136 KB, 8 arquivos | A outra máquina tem (ou pode instalar) o .NET 10: servidores, colegas programadores. A atualização do runtime fica com o sistema. |
… -r linux-x64 --self-contained | 80 MB, 195 arquivos | Máquina sem .NET, sem querer complicar. |
… -p:PublishSingleFile=true | 70 MB, 1 executável | O mesmo, mais fácil de copiar. |
… -p:PublishTrimmed=true | 14 MB, 1 executável | Menor, depois de testar bem. |
… -p:PublishAot=true | 1,3 MB, 1 executável nativo | Ferramentas de terminal, que precisam começar rápido; programas sem reflexão. |
.csproj. Em vez de lembrar as opções toda vez, ponha-as no PropertyGroup: <RuntimeIdentifier>linux-x64</RuntimeIdentifier>, <SelfContained>true</SelfContained>, <PublishSingleFile>true</PublishSingleFile>. Aí basta dotnet publish.
Quando dá errado
error NETSDK1083: O RuntimeIdentifier 'linux-64' especificado não é reconhecido.
$ dotnet publish -r linux-64 Determinando os projetos a serem restaurados... /usr/lib/dotnet/sdk/10.0.112/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(190,5): error NETSDK1083: O RuntimeIdentifier 'linux-64' especificado não é reconhecido. Consulte https://aka.ms/netsdk1083 para obter mais informações. [/home/ana/projetos/Escola/src/Escola.Relatorio/Escola.Relatorio.csproj] «… a mesma linha, mais 3 vezes …»
O que aconteceu: o RID foi digitado errado: faltou o x de x64. A mensagem se repete porque cada etapa da restauração tropeça no mesmo erro.
Como resolver: use um RID da tabela da seção 2: -r linux-x64.
You must install or update .NET to run this application.
O que aconteceu: o programa foi publicado dependente do .NET (o dotnet publish simples) e levado para uma máquina sem o runtime do .NET 10. O lançador procurou o runtime, não achou, e mostrou a mensagem que você já viu na aula O Arquivo .csproj (lá, o programa pedia o .NET 8): "você precisa instalar ou atualizar o .NET", com o runtime que faltou (Microsoft.NETCore.App, versão…) e os que ele encontrou.
Como resolver: instale o runtime na outra máquina (sudo apt install dotnet-runtime-10.0) ou publique autocontido (-r linux-x64 --self-contained).
bash: ./Escola.Relatorio: Permissão negada
O que aconteceu: o arquivo perdeu a permissão de execução no caminho. Acontece ao copiar por um pendrive formatado para Windows, ou ao baixar de um site ou e-mail, que não guardam essa permissão (veja Instalar no Linux).
Como resolver: chmod +x Escola.Relatorio. Para não perder a permissão, mande a pasta dentro de um .tar.gz, como a Ana fez na seção 1: o tar guarda as permissões.
O que aconteceu: o PublishTrimmed ou o PublishAot cortaram algo que o programa usa por reflexão. Durante a publicação, o SDK costuma avisar com warning IL2026, IL3050 ou parecidos.
Como resolver: leia os avisos IL… da publicação. Muitas bibliotecas oferecem um jeito sem reflexão (o System.Text.Json, por exemplo, tem os "geradores de código", veja JSON). Se não houver, publique sem aparar.
Referência rápida
| Comando ou opção | O que faz |
|---|---|
dotnet publish | Publica em Release, dependente do .NET, em bin/Release/net10.0/publish/. |
-r linux-x64 | Escolhe o sistema de destino (RID). |
--self-contained | Leva o runtime junto. |
-o pasta | Escolhe a pasta de saída. |
-p:PublishSingleFile=true | Um executável só. |
-p:PublishTrimmed=true | Corta o que não é usado (autocontido). |
-p:PublishAot=true | Executável nativo (precisa do clang). |
dotnet publish arquivo.cs | Publica um arquivo solto (AOT, em artifacts/). |
dotnet Programa.dll | Roda um programa dependente do .NET sem o lançador. |
Exercícios
1. Publicar
Complete o comando que prepara o programa para entregar:
2. Para o Raspberry Pi
A Ana quer publicar autocontido para um Raspberry Pi 5 com Linux de 64 bits. Complete o RID:
3. Um arquivo só
Complete a propriedade que junta tudo num executável:
4. A Ana publicou com dotnet publish (sem opções) e copiou a pasta para um computador sem .NET. O que acontece?
5. Qual forma deu o menor executável, sem precisar de .NET instalado?
6. Em que configuração o dotnet publish compila, sem -c?
Resumo
dotnet publishcompila em Release e junta numa pasta tudo o que o programa precisa para rodar em outra máquina.- O padrão é dependente do .NET: pequeno (136 KB), mas a outra máquina precisa do runtime do .NET 10.
- Autocontido (
-r linux-x64 --self-contained) leva o runtime: 80 MB, roda em qualquer Linux x64. PublishSingleFilejunta num executável;PublishTrimmedcorta o que não é usado;PublishAotgera código nativo (1,3 MB e começa em milissegundos).- O RID escolhe o sistema:
linux-x64,linux-arm64,win-x64,osx-arm64. Dá para publicar para Windows a partir do Linux (menos com AOT). - Teste sempre o executável publicado, do jeito que ele vai ser entregue.