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.

Você vai aprender
  • A usar dotnet publish e a ler o que vai na pasta publish.
  • 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 NETSDK1083 e "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).

FormaO que vai juntoA outra máquina precisa de
Dependente do .NET (o padrão)Só o seu códigoO runtime do .NET 10 instalado
Autocontido (self-contained)O seu código + o runtime inteiroNada: 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 publish usa a configuração Release por padrão (repare no bin/Release): otimizada, sem Debug.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 x de 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 com dotnet 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 .pdb valem 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 pacote Microsoft.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

RIDSistema
linux-x64Linux, Intel/AMD 64 bits (a maioria dos PCs e servidores)
linux-arm64Linux em ARM 64 bits (Raspberry Pi 4 e 5, alguns servidores)
linux-musl-x64Linux com a biblioteca musl (Alpine, comum em contêineres)
win-x64Windows, Intel/AMD 64 bits
osx-arm64macOS 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 clang para montar o executável: instale com sudo apt install clang se não tiver.
Escola.Relatorio (1,3 MB)
Um executável nativo, autocontido, sem nenhum .dll ao lado. O --self-contained nem 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 dotnet cria 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 o dotnet project convert mostrou).
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 run compila em Debug; o dotnet 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?

ComandoTamanho (o relatório da Ana)Quando usar
dotnet publish136 KB, 8 arquivosA 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-contained80 MB, 195 arquivosMáquina sem .NET, sem querer complicar.
… -p:PublishSingleFile=true70 MB, 1 executávelO mesmo, mais fácil de copiar.
… -p:PublishTrimmed=true14 MB, 1 executávelMenor, depois de testar bem.
… -p:PublishAot=true1,3 MB, 1 executável nativoFerramentas de terminal, que precisam começar rápido; programas sem reflexão.
Deixe a escolha no .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 executável aparado (ou AOT) falha, e o normal funciona

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çãoO que faz
dotnet publishPublica em Release, dependente do .NET, em bin/Release/net10.0/publish/.
-r linux-x64Escolhe o sistema de destino (RID).
--self-containedLeva o runtime junto.
-o pastaEscolhe a pasta de saída.
-p:PublishSingleFile=trueUm executável só.
-p:PublishTrimmed=trueCorta o que não é usado (autocontido).
-p:PublishAot=trueExecutável nativo (precisa do clang).
dotnet publish arquivo.csPublica um arquivo solto (AOT, em artifacts/).
dotnet Programa.dllRoda um programa dependente do .NET sem o lançador.

Exercícios

1. Publicar

Complete o comando que prepara o programa para entregar:

dotnet

2. Para o Raspberry Pi

A Ana quer publicar autocontido para um Raspberry Pi 5 com Linux de 64 bits. Complete o RID:

dotnet publish -r --self-contained

3. Um arquivo só

Complete a propriedade que junta tudo num executável:

dotnet publish -r linux-x64 --self-contained -p:=true

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 publish compila 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.
  • PublishSingleFile junta num executável; PublishTrimmed corta o que não é usado; PublishAot gera 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.