A opção de nível --output de solução não é mais válida para comandos relacionados à compilação

No SDK 7.0.200, houve uma alteração para não aceitar mais a --output/-o opção ao usar um arquivo de solução com os seguintes comandos:

  • build
  • clean
  • pack
  • publish
  • store
  • test
  • vstest

Isso ocorre porque a semântica da OutputPath propriedade, que é controlada pela --output/-o opção, não está bem definida para soluções. Os projetos construídos desta forma terão seus resultados colocados no mesmo diretório, o que é inconsistente e levou a uma série de problemas relatados pelo usuário.

Essa alteração foi reduzida para um nível de aviso de gravidade no SDK 7.0.201 e pack foi removida da lista de comandos afetados.

Versão introduzida

SDK do .NET 7.0.200, reduzido a um aviso somente no SDK 7.0.201.

Comportamento anterior

Anteriormente, se você especificasse --output/-o ao usar um arquivo de solução, a saída para todos os projetos criados seria colocada no diretório especificado em uma ordem indefinida e inconsistente.

Novo comportamento

A dotnet CLI errará se a --output/-o opção for usada com um arquivo de solução. A partir do SDK 7.0.201, um aviso será emitido e, no caso de dotnet pack nenhum aviso ou erro, será gerado.

Tipo de mudança de rutura

Essa alteração de quebra pode exigir modificações para criar scripts e pipelines de integração contínua. Como resultado, afeta a compatibilidade binária e de origem.

Razão para a alteração

Essa mudança foi feita porque a semântica da OutputPath propriedade, que é controlada pela --output/-o opção, não está bem definida para soluções. Os projetos construídos desta forma terão seus resultados colocados no mesmo diretório, o que é inconsistente e levou a uma série de problemas relatados pelo usuário.

Quando uma solução é construída com a --output opção, a propriedade é definida com o mesmo valor para todos os projetos, o OutputPath que significa que todos os projetos terão sua saída colocada no mesmo diretório. Dependendo da complexidade dos projetos na solução, resultados diferentes e inconsistentes podem ocorrer. Vamos dar uma olhada em alguns exemplos de diferentes formas de solução e como elas são afetadas por um compartilhamento OutputPath.

Projeto único, TargetFramework único

Imagine uma solução que contém um único projeto destinado a um único TargetFramework, net7.0. Nesse caso, fornecer a --output opção é equivalente a definir a OutputPath propriedade no arquivo de projeto. Durante uma compilação (ou outros comandos, mas vamos definir o escopo da discussão para construir por enquanto), todas as saídas para o projeto serão colocadas no diretório especificado.

Projeto único, vários TargetFrameworks

Agora imagine uma solução que contém um único projeto com vários TargetFrameworks, net6.0 e net7.0. Devido ao multi-targeting, o projeto será construído duas vezes, uma para cada TargetFramework. Para cada uma dessas compilações 'internas', as OutputPath serão definidas com o mesmo valor e, portanto, as saídas para cada uma das compilações internas serão colocadas no mesmo diretório. Isso significa que qualquer compilação concluída por último substituirá as saídas da outra compilação e, em um sistema de compilação paralela como o MSBuild opera por padrão, 'last' é indeterminado.

Biblioteca => Console => Teste, TargetFramework único

Agora imagine uma solução que contém um projeto de biblioteca, um projeto de console que faz referência ao projeto de biblioteca e um projeto de teste que faz referência ao projeto de console. Todos estes projetos visam um único TargetFramework, net7.0. Nesse caso, o projeto de biblioteca será criado primeiro e, em seguida, o projeto de console será construído. O projeto de teste será construído por último e fará referência ao projeto de console. Para cada projeto construído, as saídas de cada compilação serão copiadas para o diretório especificado pelo OutputPath, e, portanto, o diretório final conterá ativos de todos os três projetos. Isso funciona para testes, mas para publicação pode resultar em ativos de teste sendo enviados para a produção.

Biblioteca => Console => Teste, vários TargetFrameworks

Agora pegue a mesma cadeia de projetos e adicione uma net6.0TargetFramework compilação a eles, além da net7.0 compilação. O mesmo problema que a compilação de projeto único e com vários destinos ocorre - cópia inconsistente de ativos específicos do TFM para o diretório especificado.

Várias aplicações

Até agora, temos analisado cenários com um gráfico de dependência linear - mas muitas soluções podem conter vários aplicativos relacionados. Isso significa que vários aplicativos podem ser criados simultaneamente para a mesma pasta de saída. Se os aplicativos incluírem um arquivo de dependência com o mesmo nome, a compilação poderá falhar intermitentemente quando vários projetos tentarem gravar nesse arquivo no caminho de saída simultaneamente.

Se vários aplicativos dependerem de versões diferentes de um arquivo, mesmo que a compilação seja bem-sucedida, qual versão do arquivo é copiada para o caminho de saída pode ser não determinística. Isso pode acontecer quando os projetos dependem (possivelmente transitivamente) de diferentes versões de um pacote NuGet. Dentro de um único projeto, o NuGet ajuda a garantir que suas dependências (incluindo quaisquer dependências transitivas por meio de pacotes NuGet e/ou referências de projeto) sejam unificadas para a mesma versão. Como a unificação é feita dentro do contexto de um único projeto e seus projetos dependentes, isso significa que é possível resolver diferentes versões de um pacote quando dois projetos de nível superior separados são construídos. Se o projeto que depende da versão superior copiar a dependência por último, então geralmente os aplicativos serão executados com êxito. No entanto, se a versão inferior for copiada por último, o aplicativo que foi compilado em relação à versão superior não conseguirá carregar o assembly em tempo de execução. Como a versão copiada pode ser não determinística, isso pode levar a compilações esporádicas e não confiáveis, onde é muito difícil diagnosticar o problema.

Outros exemplos

Para obter mais exemplos de como esse erro subjacente se apresenta na prática, consulte a discussão em dotnet/sdk#15607.

A recomendação geral é executar a ação que você executou anteriormente sem a --output/-o opção e, em seguida, mover a saída para o local desejado após a conclusão do comando. Também é possível realizar a ação em um projeto específico e ainda aplicar a --output/-o opção, pois isso tem semântica mais bem definida.

Se você quiser manter o comportamento existente exatamente, então você pode usar o --property sinalizador para definir uma propriedade MSBuild para o diretório desejado. A propriedade a ser usada varia de acordo com o comando:

Comando Property Exemplo
build OutputPath dotnet build --property:OutputPath=DESIRED_PATH
clean OutputPath dotnet clean --property:OutputPath=DESIRED_PATH
pack PackageOutputPath dotnet pack --property:PackageOutputPath=DESIRED_PATH
publish PublishDir dotnet publish --property:PublishDir=DESIRED_PATH
store OutputPath dotnet store --property:OutputPath=DESIRED_PATH
test TestResultsDirectory dotnet test --property:OutputPath=DESIRED_PATH

NOTA Para obter melhores resultados, o DESIRED_PATH deve ser um caminho absoluto. Os caminhos relativos serão 'ancorados' (ou seja, tornados absolutos) de maneiras que você pode não esperar, e podem não funcionar da mesma forma com todos os comandos.