Guia do mantenedor

Este documento descreve políticas, diretrizes e melhores práticas a seguir ao fazer contribuições para o vcpkg.

Destina-se a desempenhar o mesmo papel que:

Objetivos de projeto de registos selecionados

As portas devem ser instaladas simultaneamente

Uma porta incluída no registo curado deve ser construída com sucesso na presença de todas as outras portas do registo. Toda a combinação de portas é testada regularmente pelo CI automatizado do vcpkg.

Uma porta não deve executar nenhuma das seguintes operações:

Existem exceções para:

  • Portas que substituem outra porta a título oficial. Isto inclui casos em que:
    • O mesmo projeto publica um componente sucessor.
    • Outro projeto assume a manutenção de um componente abandonado, incluindo através de um fork.
    • Outro projeto publica um componente sucessor.
  • Portos onde não há sobreposição de plataformas. Por exemplo, um substituto apenas para Windows de um componente Linux que não suporta Windows.
  • Ports que precedem esta política e que têm um número demonstravelmente significativo de utilizadores e contribuintes.

Os responsáveis pelo VCPKG avaliam exceções de manutenção do regime anterior caso a caso. Por exemplo, boringssl foi adicionado antes de existirem registos personalizados e permanece no registo curado porque ainda tem utilizadores relevantes, apesar de openssl ser o fornecedor canónico da família de forks OpenSSL. A aceitação prévia por si só não garante uma exceção.

Ports que não cumprem esta política não podem ser aceites no registo curado.

Sugestão

Se a sua porta não puder ser incluída no registo curado, considere publicar um registo personalizado ou usar uma porta sobreposta.

Os ports devem ser testados em pelo menos um triplo oficial

Todas as portas do repositório curado devem ser testadas em CI para pelo menos um triplet oficial.

Existem exceções para:

  • Circunstâncias temporárias, tais como:
    • A porta não é construída sem uma atualização da infraestrutura CI.
    • O porto não compila devido a uma alteração recente que causa falha numa dependência a montante, estando a decorrer esforços para resolver a situação.
  • Ports que precedem esta política e que têm um número demonstravelmente significativo de utilizadores e contribuintes.

Novas portas que não podem ser testadas em pelo menos um trio oficial não são aceites no registo curado. As portas existentes que já não cumprem esta política e não se qualificam para uma exceção acima referida são retiradas do registo. Por exemplo, se o teste de CI tiver sido desativado em todos os tripletos oficiais que uma porta suporta, e não se aplicar exceção, a porta é retirada.

Sugestão

Se a sua porta não puder ser incluída no registo curado, considere publicar um registo personalizado ou usar uma porta sobreposta.

Os componentes empacotados devem ser estáveis e mantidos ativamente

Os componentes publicados no registo curado devem estar em manutenção ativa. Os portos para componentes inativos podem ser retirados da lista.

Um componente é considerado inativo se:

  • Os seus responsáveis pela manutenção declararam o componente abandonado.
  • O seu repositório de origem está arquivado ou já não aceita contribuições.
  • Os mantenedores não respondem ou são inacessíveis.
  • Não foram feitas mudanças significativas há muito tempo.

Existem exceções para componentes fundamentais considerados maduros e estáveis e que não sofrem alterações frequentemente. Por exemplo: zlib.

Os componentes empacotados devem ser estáveis

Os componentes embalados no registo selecionado devem ser maduros e destinados ao consumo por utilizadores de vcpkg. Os componentes destinados a uso pessoal devem ser publicados em registos privados.

Um componente é considerado suficientemente maduro para o registo curado, se uma destas afirmações for verdadeira:

  • O componente tem uma release com pelo menos seis meses.
  • O componente demonstra pelo menos seis meses de desenvolvimento ativo em público.
  • O componente é uma parte oficial de um projeto com pelo menos seis meses de lançamentos ou desenvolvimento público ativo. Por exemplo, uma nova biblioteca Boost ou componente Qt.
  • O componente demonstra maturidade equivalente aos requisitos anteriores noutra capacidade.

Alguns indicadores de imaturidade dos componentes são:

  • O componente não aparece nos motores de busca.
  • O componente é frequentemente renomeado.
  • Conflitos com outras bibliotecas.

Sugestão

Se a sua porta não puder ser incluída no registo curado, considere publicar um registo personalizado ou usar uma porta sobreposta.

Estrutura de Relações Públicas

Faça solicitações pull separadas por porta

Requests de Pull (PRs) mais pequenos são mais fáceis de revisar. Os pull requests devem fazer alterações numa única porta. Isto também reduz os tempos de espera pelos resultados da Integração Contínua (CI).

Limitar PRs a uma única porta pode não ser possível em alguns casos. Por exemplo, ao alterar uma porta, é necessário atualizar ou aplicar patches aos consumidores posteriores.

Evite alterações estéticas não relacionadas

Não faças alterações cosméticas não relacionadas a um port que de outra forma não está modificado, como formatar, renomear variáveis ou corrigir erros tipográficos. Estas alterações consomem tempo de computação que é melhor utilizado noutros locais. Alterações que melhoram a correção, compatibilidade, fiabilidade ou reprodutibilidade da compilação não são consideradas triviais apenas porque os ficheiros instalados permanecem inalterados.

Use nomes de porta distintos

O nome de um porto deve indicar o seu conteúdo.

Pesquisar o nome da porta num motor de busca ou em navegadores de pacotes especializados, como o Repology, deve conduzir ao seu componente correspondente.

Portos com nomes curtos ou que têm nomes comuns requerem desambiguação. Isto aplica-se apenas ao nome do port no registo curado; o nome do componente embalado e o seu conteúdo não são obrigados a cumprir esta política.

Existem exceções para portas que empacotam um componente com uma forte associação ao nome da sua porta. Por exemplo: libpng, openssl, ou zlib.

Para cumprir esta política, novas portas com nomes ambíguos podem usar um prefixo, tais como:

  • O proprietário, nome de utilizador ou organização do repositório. Exemplo: google-cloud-cpp. Para adaptações de componentes alojados no GitHub, o proprietário do GitHub é um prefixo inequívoco <github owner>-<repository name>aceitável.
  • O nome de uma suíte a que o pacote pertence: boost-dll. Só se o pacote for um componente oficial desse conjunto.

Exemplo: Nome de porta ambíguo

Um port com este nome ip é considerado ambíguo porque:

  • O nome é demasiado curto,
  • o nome é uma palavra comum, e
  • O nome não está fortemente associado a nenhum componente singular.

Para determinar se um nome é ambíguo, remova os seguintes prefixos e sufixos comuns usados pelos componentes C++ e open source do nome da porta:

  • cpp
  • free
  • lib
  • open
  • numerais

Por exemplo: ip-cpp, libip e ip5 são ambíguos porque estão reduzidos ao mesmo ip radical.

Limitar renomeações de portas

Para evitar confusão para os utilizadores, uma porta não deve ser renomeada mais do que uma vez em qualquer período de 12 meses.

Usar RPs de rascunho do GitHub

Os Rascunhos de PR do GitHub são uma ótima maneira de obter feedback humano ou de CI sobre o trabalho que ainda não está pronto para ser integrado. A maioria das novas RP deve ser aberta como rascunho e convertida em RP normal assim que a IC for aprovada.

Para obter mais informações sobre PRs de rascunho do GitHub, consulte Introducing draft pull requests.

Um mantenedor do vcpkg pode converter a sua PR em rascunho durante o processo de revisão, normalmente ao pedir alterações ou deixar comentários indicando quando deve marcar a PR como pronta para revisão.

Fechar RPs inativos

Um mantenedor de vcpkg pode encerrar uma PR depois de esta ter estado à espera de ação do contribuinte durante mais de 60 dias. A contagem decrescente da inatividade decorre apenas enquanto for necessária uma ação do contribuidor. Não é executado enquanto o PR está à espera da verificação do laboratório de construção ou de revisão por um mantenedor do vcpkg.

Um PR está em estado de revisão quando:

  • Não tem verificações de RP falhadas, nem o colaborador pede ajuda ou justifica a razão das falhas.
  • Não tem qualquer pedido de alteração ou esclarecimento pendente por parte de um mantenedor do vcpkg.
  • Não tem conflitos de fusão.

A contagem decrescente começa quando o PR entra num estado que exige ação do contribuinte. Isto pode acontecer quando uma PR é aberta com problemas que o contribuinte precisa de resolver, ou quando um mantenedor do vcpkg solicita alterações ou esclarecimentos. Um mantenedor do vcpkg pode marcar a PR como um rascunho ao solicitar ação do contribuinte.

Uma resposta substancial do contribuinte reinicia a contagem decrescente. Se a resposta resolver todos os pedidos pendentes e o PR estiver pronto para revisão, a contagem decrescente para até que seja necessária novamente uma ação do contribuinte. Se o contribuidor não responder no prazo de 60 dias enquanto for necessária uma ação, o PR é considerado obsoleto e pode ser encerrado a critério do responsável pelo vcpkg.

Portfiles

Evite funções auxiliares preteridas

Neste momento, os seguintes auxiliares são preteridos:

Algumas das funções auxiliares de substituição estão em "portas de ferramentas" para permitir que os consumidores fixem seu comportamento em versões específicas, para permitir bloquear o comportamento dos auxiliares em uma versão específica. As portas de ferramentas precisam ser adicionadas ao "dependencies"da sua porta, da seguinte forma:

{
  "name": "vcpkg-cmake",
  "host": true
},
{
  "name": "vcpkg-cmake-config",
  "host": true
}

Evite comentários excessivos em portfiles

Idealmente, os portfiles devem ser curtos, simples e o mais declarativos possível. Remova todos os comentários padrão introduzidos pelo comando create antes de enviar um PR.

Use minúsculas para sequências de dígitos hexadecimais

Muitos dos recursos do vcpkg dependem da comparação de cadeias de caracteres de dígitos hexadecimais. Alguns exemplos incluem, mas não estão limitados a, hashes SHA512, IDs de confirmação do Git e hashes de objeto de árvore.

Internamente, vcpkg usa normalização minúscula para comparações de tais valores onde o invólucro é irrelevante. No entanto, as ferramentas construídas sobre a infraestrutura do vcpkg podem não ter as mesmas considerações. Por esta razão, necessitamos de cadeias hexadecimais

a ser rebaixado para consistência nos seguintes cenários:

  • O parâmetro SHA512 nas funções utilitárias do vcpkg.
  • O REF parâmetro nas funções auxiliares vcpkg, quando o valor é uma cadeia hexadecimal.
  • O git-tree objeto em ficheiros de base de dados de versão.
  • O sha512 objeto no scripts/vcpkg-tools.json arquivo.
  • Outros locais onde o invólucro da cadeia hexadecimal não é importante.

As portas não devem depender do caminho

As portas não devem alterar seu comportamento com base em quais portas já estão instaladas em um formato que alteraria o conteúdo que a porta instala. Por exemplo, dado:

> vcpkg install a
> vcpkg install b
> vcpkg remove a

e ainda

> vcpkg install b

Os arquivos instalados por b devem ser os mesmos, independentemente da influência da instalação anterior do a. Isso significa que as portas não devem tentar detetar se algo é fornecido na árvore instalada por outra porta antes de tomar qualquer ação. Uma causa específica e comum desse comportamento "dependente de caminho" é descrita abaixo em "Ao definir recursos, controle explicitamente as dependências".

Regra de atribuição de porta exclusiva

Em todo o sistema vcpkg, duas portas que um utilizador possa querer usar ao mesmo tempo não devem fornecer o mesmo ficheiro. Se um port tentar instalar um ficheiro já fornecido por outro port, a instalação falhará. Se uma porta quiser usar um nome extremamente comum para um cabeçalho, por exemplo, ela deve colocar esses cabeçalhos em um subdiretório em vez de em include.

Esta propriedade é verificada regularmente por execuções de integração contínua que tentam instalar todas as portas no registro, que falhará com FILE_CONFLICTS se duas portas fornecerem o mesmo arquivo.

Adicionar exportações CMake em um namespace não oficial

Um ideal de design central do vcpkg é não criar "lock-in" para os usuários. No sistema de compilação, não deve haver diferença entre depender de uma biblioteca do sistema e depender de uma biblioteca do vcpkg. Para esse fim, evitamos adicionar exportações ou alvos do CMake às bibliotecas existentes com "o nome óbvio", para permitir que projetos upstream adicionem suas próprias exportações oficiais do CMake sem entrar em conflito com o vcpkg.

Para esse fim, todas as configurações do CMake que a porta exporta, que não estão na biblioteca de origem, devem ter unofficial- como um prefixo. Quaisquer destinos adicionais devem estar no espaço de nomes unofficial::<port>::.

Isso significa que o usuário deve ver:

  • find_package(unofficial-<port> CONFIG) é o método para obter o pacote exclusivo do vcpkg
  • unofficial::<port>::<target> como destino exportado a partir dessa porta.

Exemplos:

  • brotli cria o pacote unofficial-brotli, produzindo o destino unofficial::brotli::brotli.

Cada porta tem que fornecer um arquivo chamado copyright na pasta ${CURRENT_PACKAGES_DIR}/share/${PORT}. Se o conteúdo da licença de um pacote estiver disponível em seus arquivos de origem, esse arquivo deverá ser criado por uma chamada para vcpkg_install_copyright(). vcpkg_install_copyright também agrupa vários arquivos de direitos autorais, se necessário.

vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

Um método mais antigo para criar manualmente esse arquivo é com o comando file interno do CMake. Isso é desencorajado em favor de vcpkg_install_copyright em novos portos, mas ainda é permitido.

file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)

Se o conteúdo da licença nos arquivos de origem upstream não estiver em forma de texto (por exemplo, um arquivo PDF), copyright deve conter uma explicação sobre como um usuário pode encontrar os requisitos de licença. Se possível, ele também deve incluir um link para os arquivos de origem originais indicando isso, para que os usuários possam verificar se ele está atualizado.

file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" [[As of 2023-07-25, according to
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/README.md#end-user-license-agreement
this software is bound by the "SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT" PDF located at
https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Public-Documents/ADL%20SDK%20EULA.pdf
]])

Restrições de versão em portas

As restrições de versão dentro das portas devem geralmente ser evitadas, pois podem dificultar a evolução independente dos componentes. A adição de tais restrições só é permitida quando existe uma justificação bem documentada, como a incompatibilidade comprovada com versões anteriores específicas. Estas restrições não devem ser usadas apenas para manter a paridade com componentes independentes.

As variáveis em MAYBE_UNUSED_VARIABLES devem ser aplicadas a pelo menos uma configuração

Ao adicionar uma nova variável para MAYBE_UNUSED_VARIABLES silenciar um aviso durante a etapa de configuração do CMake, você deve adicionar um comentário explicando o caso quando a nova variável se aplica. Se uma variável não se aplica em nenhuma configuração, então é muito provável que exista um bug subjacente (por exemplo, um nome de variável mal escrito) e adicioná-la não tem qualquer efeito real na compilação.

vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
  FEATURES
    windowsfeature WINDOWS_OPTION
)

vcpkg_cmake_configure(
  SOURCE_PATH "${SOURCE_PATH}"
  OPTIONS
    ${FEATURE_OPTIONS}
  MAYBE_UNUSED_VARIABLES
    WINDOWS_OPTION # Applies only on Windows
)

Caraterísticas

Não use recursos para implementar alternativas

Os recursos devem ser tratados como funcionalidade aditiva. Se port[featureA] instala e port[featureB] instala, então port[featureA,featureB] deve instalar. Além disso, se uma segunda porta depende de [featureA] e uma terceira porta depende de [featureB], a instalação tanto da segunda quanto da terceira porta deve ter suas dependências satisfeitas.

As bibliotecas nessa situação devem escolher uma das opções disponíveis, conforme expresso em vcpkg, e os usuários que desejam uma configuração diferente devem usar portas de sobreposição neste momento.

Exemplos existentes que não aceitaríamos hoje serão mantidos para compatibilidade retroativa.

  • libgit2, libzip, open62541 todos têm recursos para selecionar um back-end TLS ou cripto. curl tem diferentes opções de back-end de criptografia, mas permite selecionar entre elas em tempo de execução, o que significa que o princípio acima é mantido.
  • darknet tem opencv2, opencv3, recursos para controlar qual versão do OpenCV usar para suas dependências.

Um recurso pode ativar a funcionalidade de visualização ou beta

Não obstante o acima, se houver uma ramificação de visualização ou similar em que a funcionalidade de visualização tenha uma alta probabilidade de não interromper a funcionalidade de não visualização (por exemplo, sem remoções de API), um recurso é aceitável para modelar essa configuração.

Exemplos:

  • Os SDKs do Azure (do formato azure-Xxx) têm um recurso public-preview.
  • imgui tem uma funcionalidade de experimental-docking que envolve a sua ramificação de pré-visualização, a qual utiliza um commit de mesclagem associado a cada uma das suas versões públicas numeradas.

Os recursos padrão não devem adicionar APIs

Observação

Uma funcionalidade que está ativada por padrão pelo sistema de build a montante não implica que a funcionalidade deva ser adicionada às entradas default-features. Como o objetivo pretendido de default-features não é modelar as decisões tomadas pelo upstream, mas sim proporcionar conveniência para os utilizadores do modo clássico.

Os recursos padrão destinam-se a garantir que uma compilação razoavelmente funcional de uma biblioteca seja instalada para clientes que não sabem que estão usando-a. Se eles não sabem que estão usando uma biblioteca, eles não podem saber listar recursos. Por exemplo, libarchive expõe recursos que permitem algoritmos de compactação a uma interface genérica existente; Se construído sem qualquer um desses recursos, a biblioteca pode não ter utilidade.

Aqui, os clientes incluem tanto projetos de utilizador que consomem o gráfico do pacote como ports dependentes dentro desse gráfico. Ambos expressam os seus requisitos de dependência através da mesma interface da funcionalidade.

Considere cuidadosamente se uma funcionalidade deve estar ativada por defeito, porque desativar funcionalidades ativadas por defeito é complexo.

Para desativar funcionalidades padrão para uma dependência transitiva:

  • Todo cliente que depende do port deve desativar as suas funcionalidades predefinidas na sua declaração de dependência usando "default-features": false.
  • O projeto de topo deve também nomear diretamente a dependência transitiva, seja no seu manifesto com "default-features": false, ou na vcpkg install linha de comandos com [core].

Desativar funcionalidades predefinidas não impede outro cliente de solicitar explicitamente qualquer uma dessas funcionalidades.

Para esta política, uma API é qualquer interface exposta aos consumidores, incluindo interfaces de biblioteca, interfaces de linha de comandos executáveis e outras interfaces binárias. Uma funcionalidade pode ser ativada por defeito quando adiciona comportamento por detrás de uma API existente, como o suporte a algoritmos de compressão adicionais através de uma interface genérica existente.

No registo curado do vcpkg, se uma funcionalidade adicionar uma API, deve estar desativada por predefinição. Em caso de dúvida, não marque um recurso como padrão.

Não use recursos para controlar alternativas em interfaces publicadas

Se um consumidor de uma porta depende apenas da funcionalidade principal dessa porta, há uma alta probabilidade de que eles não serão afetados ao ativar a funcionalidade. Isso é ainda mais importante quando a alternativa não é controlada diretamente pelo consumidor, mas por configurações de compilador como /std:c++17 / -std=c++17.

Exemplos existentes que não aceitaríamos hoje serão mantidos para compatibilidade retroativa.

  • redis-plus-plus[cxx17] controla um polyfill, mas não incorpora a configuração na árvore instalada.
  • ace[wchar] altera todas as APIs para aceitar const wchar_t* em vez de const char*.

Um recurso pode substituir "polyfills" por "aliases", desde que a substituição esteja incorporada na árvore instalada.

Tendo em conta o exposto acima, as portas podem remover polyfills com uma característica, desde que:

  1. Ativar a funcionalidade transforma os polyfills em alias da entidade coberta por polyfill.
  2. O estado do polyfill é incorporado nos cabeçalhos instalados, de modo que se tornem improváveis os erros "impossíveis" de incompatibilidade ABI em tempo de execução.
  3. É possível para um utilizador da porta escrever código que funcione em ambos os modos, por exemplo, utilizando um typedef que seja polyfill ou não.

Exemplo:

  • abseil[cxx17] altera absl::string_view a uma substituição ou std::string_view; o patch implementa o requisito de cozedura.

Se for crítico expor as alternativas subjacentes, recomendamos fornecer mensagens no momento da compilação para instruir o usuário sobre como copiar a porta em uma sobreposição privada:

set(USING_DOG 0)
message(STATUS "This version of LibContoso uses the Kittens backend. To use the Dog backend instead, create an overlay port of this with USING_DOG set to 1 and the `kittens` dependency replaced with `dog`.")
message(STATUS "This recipe is at ${CMAKE_CURRENT_LIST_DIR}")
message(STATUS "See the overlay ports documentation at https://github.com/microsoft/vcpkg/blob/master/docs/specifications/ports-overlay.md")

Técnicas de construção

Não use dependências de fornecedores

É preferível empacotar as dependências separadamente para que os utilizadores possam selecioná-las e atualizá-las através do vcpkg. As dependências incluídas no projeto apresentam vários desafios:

  • Dificuldade em atualizar: Dependências incorporadas são mais difíceis de acompanhar e atualizar, incluindo ao aplicar correções de segurança.
  • Conflitos de símbolos: Uma dependência incorporada pode definir os mesmos símbolos que outra versão da dependência, causando erros em tempo de execução ou comportamentos indefinidos.
  • Conformidade com licenciamentos: Dependências incorporadas podem obscurecer os requisitos de licenciamento ou criar problemas de compatibilidade de licenças.
  • Manutenção aumentada: Manter as dependências incorporadas sincronizadas com as suas versões upstream aumenta o trabalho de manutenção e pode duplicar trabalho entre portabilidades.

Por estas razões, elimina a dependência de fornecedores sempre que possível. As dependências incorporadas não devem estar expostas ao domínio de ligação do utilizador.

Um domínio de ligação é o conjunto de símbolos que podem ser resolvidos entre si em tempo de execução. No Windows, cada DLL geralmente tem o seu próprio link domain. Em plataformas com uma tabela de símbolos para todo o processo, o domínio de ligação inclui geralmente todo o processo.

Se um componente expuser uma dependência incorporada ao mesmo domínio de ligação que uma dependência selecionada através do vcpkg, ambas as cópias podem definir os mesmos símbolos. Isto viola a Regra de Definição Única e pode causar erros em tempo de execução ou comportamentos indefinidos. O problema pode surgir apenas quando as duas versões diferem o suficiente para serem incompatíveis.

Uma dependência incorporada só é permitida quando está totalmente isolada dos domínios de ligação usados por ports downstream e aplicações de utilizador. Por exemplo, uma ferramenta de build apenas para o host pode conter uma dependência ligada estaticamente se os binários a jusante não se ligarem à ferramenta e os símbolos da dependência não puderem entrar nos domínios de ligação.

Prefira usar o CMake

Quando vários sistemas de compilação estiverem disponíveis, prefira usar o CMake. Além disso, quando apropriado, pode ser mais simples e mais fácil de manter reescrever os sistemas de compilação alternativos em CMake usando diretivas file(GLOB).

Exemplos: abseil

Escolha binários estáticos ou compartilhados

Ao criar bibliotecas CMake, vcpkg_cmake_configure() passará o valor correto para BUILD_SHARED_LIBS com base na variante solicitada pelo usuário.

Você pode calcular parâmetros de configuração alternativos usando string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" ...).

# portfile.cmake

string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" KEYSTONE_BUILD_STATIC)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" KEYSTONE_BUILD_SHARED)

vcpkg_cmake_configure(
    SOURCE_PATH ${SOURCE_PATH}
    OPTIONS
        -DKEYSTONE_BUILD_STATIC=${KEYSTONE_BUILD_STATIC}
        -DKEYSTONE_BUILD_SHARED=${KEYSTONE_BUILD_SHARED}
)

Se uma biblioteca não oferecer opções de configuração para selecionar a variante de compilação, a compilação deverá ser corrigida. Ao aplicar patches em uma compilação, você deve sempre tentar maximizar a capacidade de manutenção futura da porta. Normalmente, isso significa minimizar o número de linhas que precisam ser alteradas para corrigir o problema em questão.

Exemplo: Aplicar patches em uma biblioteca CMake para evitar a criação de variantes indesejadas

Por exemplo, ao aplicar patches numa biblioteca com base em CMake, pode ser o suficiente adicionar EXCLUDE_FROM_ALL a destinos indesejados e envolver a chamada install(TARGETS ...) num if(BUILD_SHARED_LIBS). Isso será mais curto do que quebrar ou excluir todas as linhas que mencionam a variante indesejada.

Para um projeto CMakeLists.txt com o seguinte conteúdo:

add_library(contoso SHARED contoso.c)
add_library(contoso_static STATIC contoso.c)

install(TARGETS contoso contoso_static EXPORT ContosoTargets)

install(EXPORT ContosoTargets
  FILE ContosoTargets
  NAMESPACE contoso::
  DESTINATION share/contoso)

Substitua a install(TARGETS) linha por um bloco condicional que também exclua o alvo indesejado da build padrão:

add_library(contoso SHARED contoso.c)
add_library(contoso_static STATIC contoso.c)

if(BUILD_SHARED_LIBS)
  set_target_properties(contoso_static PROPERTIES EXCLUDE_FROM_ALL 1)
  install(TARGETS contoso EXPORT ContosoTargets)
else()
  set_target_properties(contoso PROPERTIES EXCLUDE_FROM_ALL 1)
  install(TARGETS contoso_static EXPORT ContosoTargets)
endif()

install(EXPORT ContosoTargets
  FILE ContosoTargets
  NAMESPACE contoso::
  DESTINATION share/contoso)

Ao definir recursos, controle explicitamente as dependências

Ao definir um recurso que captura uma dependência opcional, certifique-se de que a dependência não será usada acidentalmente quando o recurso não estiver explicitamente habilitado.

set(CMAKE_DISABLE_FIND_PACKAGE_ZLIB ON)
set(CMAKE_REQUIRE_FIND_PACKAGE_ZLIB OFF)
if ("zlib" IN_LIST FEATURES)
  set(CMAKE_DISABLE_FIND_PACKAGE_ZLIB OFF)
  set(CMAKE_REQUIRE_FIND_PACKAGE_ZLIB ON)
endif()

vcpkg_cmake_configure(
  SOURCE_PATH ${SOURCE_PATH}
  OPTIONS
    -DCMAKE_DISABLE_FIND_PACKAGE_ZLIB=${CMAKE_DISABLE_FIND_PACKAGE_ZLIB}
    -DCMAKE_REQUIRE_FIND_PACKAGE_ZLIB=${CMAKE_REQUIRE_FIND_PACKAGE_ZLIB}
)

O trecho abaixo usando vcpkg_check_features() é equivalente.

vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
  FEATURES
    "zlib"    CMAKE_REQUIRE_FIND_PACKAGE_ZLIB
  INVERTED_FEATURES
    "zlib"    CMAKE_DISABLE_FIND_PACKAGE_ZLIB
)

vcpkg_cmake_configure(
    SOURCE_PATH ${SOURCE_PATH}
    OPTIONS
      ${FEATURE_OPTIONS}
)

ZLIB no trecho diferencia maiúsculas de minúsculas. Para obter mais informações, consulte a documentação CMAKE_DISABLE_FIND_PACKAGE_<PackageName> e CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>.

Um ficheiro de entrada de linker instalado em lib ou debug/lib, como um ficheiro .lib, .a, .so ou .dylib, é considerado conflituoso se fizer alguma das seguintes ações:

  • Define main.
  • Define malloc.
  • Define os símbolos que também são definidos noutras bibliotecas.

Alguns ficheiros de entrada do linker fornecem intencionalmente substituições com âmbito restrito para símbolos que os consumidores, de outra forma, definiriam ou obteriam noutro local. Por exemplo, o Google Test gtest-main.lib disponibiliza main para executáveis de teste cujos autores optam por usá-lo em vez de definirem o seu próprio ponto de entrada.

Coloque ficheiros de entrada de linker conflitantes em lib/manual-link ou debug/lib/manual-link para que os sistemas de compilação que ligam automaticamente ficheiros do diretório da biblioteca não os selecionem. As DLLs do Windows mantêm-se em bin ou debug/bin; coloque as respetivas bibliotecas de importação em manual-link quando for necessária a ligação explícita. Os consumidores devem optar explicitamente por associar estes ficheiros e são responsáveis por evitar definições conflitantes. Esta exceção não é um mecanismo geral para empacotar implementações alternativas de outro componente, e mover um ficheiro de entrada do linker para manual-link dentro não isola os seus símbolos do domínio de ligação do consumidor.

Instalando binários pré-construídos

Ports que instalam artefactos pré-construídos (apenas binários) são permitidas, mas fortemente desencorajadas, desde que os artefactos não impeçam os utilizadores de selecionar ou atualizar dependências através do vcpkg. A construção a partir do código-fonte é preferida porque respeita as definições do vcpkg que controlam o compilador e os flags.

Rejeitaremos portos que cumpram todas as seguintes condições:

  1. Instale binários pré-construídos em vez de criar a partir do código-fonte, e
  2. Esses binários incorporam ou requerem em tempo de execução dependências fornecidas por outros ports no registo curado, e
  3. Os artefactos instalados expõem essas dependências a um link domain que também pode conter versões selecionadas através do vcpkg.

Esta combinação bloqueia efetivamente o grafo de dependência para as versões utilizadas quando o binário pré-compilado upstream foi produzido. Se o vcpkg atualizar uma dependência como zlib ou openssl, as versões pré-construídas e selecionadas pelo utilizador podem definir símbolos incompatíveis no mesmo domínio de ligação. Isto pode causar violações da Regra de Definição Única, incompatibilidades de ABI ou atualizações de segurança perdidas.

Um artefacto expõe uma dependência sempre que essa dependência participa no domínio de ligação de um utilizador, independentemente de upstream pretender expô-la. Isto inclui:

  • Bibliotecas associáveis pelo utilizador .lib, .a, .so, .dylib, ou bibliotecas de importação que trazem a dependência para o domínio de ligação do utilizador.
  • Cabeçalhos instalados que referenciam (diretamente ou através de código inline/template) símbolos, tipos ou macros de outros ports do vcpkg.
  • Ficheiros CMake config ou pkg-config instalados que invocam find_dependency() ou Requires: noutras portas vcpkg.

Cenários permitidos (mas ainda desencorajados):

  • Ferramentas auxiliares apenas de host usadas em tempo de compilação, cujas saídas são consumidas mas com as quais as ports dependentes não estabelecem ligação, desde que isolem dependências agrupadas ou dependam apenas de bibliotecas de execução do sistema ubíquas.
  • Bibliotecas pré-construídas cujas dependências agrupadas permanecem inteiramente em domínios de ligação separados e não aparecem em cabeçalhos instalados ou interfaces exportadas. A ligação estática por si só não isola uma dependência do domínio de ligação do utilizador.
  • Somente dados, firmware ou pacotes de recursos não vinculados ao código do usuário.

Exemplos proibidos:

  • Uma biblioteca pré-construída que liga estaticamente uma cópia privada de zlib e expõe os seus símbolos a um domínio de ligação que também pode conter a versão zlib selecionada através do vcpkg.
  • Um libfoo pré-construído que instala lib/libfoo.lib juntamente com cabeçalhos, incluindo <zlib.h>, e foi compilado contra uma versão específica de zlib; os consumidores depois fazem a ligação a libfoo, esperando compatibilidade.
  • Um SDK pré-compilado que instala um ficheiro de pacote do CMake chamando find_dependency(OpenSSL) enquanto o binário foi compilado com base numa versão mais antiga do OpenSSL.

Mitigações / alternativas:

  • Forneça uma compilação a partir do código-fonte utilizando scripts upstream ou adicione um wrapper fino de CMake.
  • Peça à equipa responsável pelo código-fonte para publicar uma versão baseada no código-fonte ou instruções de compilação reproduzíveis; inclua o problema ou PR na origem num comentário em portfile.cmake ou vcpkg.json.
  • Use uma porta de sobreposição ou um registo privado para pré-compilados específicos da organização que não podem satisfazer essas regras.

Versionamento

Siga as convenções comuns para o campo "version"

Ao criar uma nova porta, siga a convenção de controle de versão usada pelo autor do pacote. Ao atualizar a porta, continue a usar a mesma convenção, a menos que o upstream diga o contrário. Para obter uma explicação completa de nossas convenções, consulte nossa documentação de controle de versão .

Se o upstream não publicar uma versão há algum tempo, não altere o esquema de versionamento da porta para version-date para obter as alterações mais recentes. Esses compromissos podem incluir alterações que não estão prontas para produção. Em vez disso, solicite ao repositório de origem que publique uma nova versão.

Atualize o "port-version" campo para cada porta modificada

Cada PR que modifica um port deve produzir uma nova versão desse port. Os PRs são integrados com squash merge, por isso atualize a versão de cada porta modificada uma vez por PR, não uma vez por commit ou revisão.

Se a versão a montante não mudou, incrementa o "port-version" campo. Se a versão a montante tiver mudado, reinicie "port-version" para zero removendo o campo ou definindo-o para 0.

Por exemplo:

  • A versão do pacote Zlib está atualmente 1.2.1, sem "port-version" explícito (equivalente a um "port-version" de 0).
  • Você descobriu que o arquivo de direitos autorais errado foi implantado e corrigiu isso no portfile.
  • Você deve atualizar o campo "port-version" no arquivo de manifesto para 1.

Consulte a documentação de controlo de versão para obter mais informações.

Atualize os ficheiros versions/ de versão para cada port modificado

O VCPKG usa um conjunto de arquivos de metadados para potencializar seu recurso de controle de versão. Esses arquivos estão localizados nos seguintes locais:

  • ${VCPKG_ROOT}/versions/baseline.json, (este ficheiro é comum a todas as portas) e
  • ${VCPKG_ROOT}/versions/${first-letter-of-portname}-/${portname}.json (uma por porta).

Por exemplo, para zlib os ficheiros relevantes são:

  • ${VCPKG_ROOT}/versions/baseline.json
  • ${VCPKG_ROOT}/versions/z-/zlib.json

Para cada port modificado numa PR, atualize os seus ficheiros de versão para registar a árvore Git final. Se fizer mais alterações durante a revisão, execute vcpkg x-add-version <port-name> --overwrite-version para atualizar a entrada da versão por integrar

sem aumentar "port-version" novamente.

O método recomendado para atualizar esses arquivos é executar o comando x-add-version, por exemplo:

vcpkg x-add-version zlib

Se você estiver atualizando várias portas ao mesmo tempo, em vez disso, poderá executar:

vcpkg x-add-version --all

para atualizar os arquivos de todas as portas modificadas de uma só vez.

Para obter mais informações, consulte o de referência de controle de versão e artigos Registros.

Aplicação de patches

VCPKG é uma solução de embalagem, não os proprietários finais dos componentes que implantamos. Precisamos aplicar patches em alguns casos para melhorar a compatibilidade de componentes com plataformas, ou compatibilidade de componentes entre si.

  • Queremos evitar patches que:
    • a entidade superior discordaria
    • causar vulnerabilidades ou falhas
    • somos incapazes de manter atualizações de versão upstream
    • são grandes o suficiente para causar complicações de licença com o repositório vcpkg em si próprio

Notificação prévia e períodos de espera

Os responsáveis upstream são quem tem a decisão final sobre o comportamento do seu componente. Os mantenedores do vcpkg exigem notificação ao projeto upstream para patches relevantes para o upstream e para bugs identificados por ferramentas de revisão de código por IA em submissões de novos ports.

Patches relevantes para upstream

Se um patch puder ser útil para o upstream, os mantenedores do vcpkg exigem que o upstream seja notificado do conteúdo do patch. Patches que aplicam comportamentos específicos do vcpkg e não relacionados com upstream, como a desincorporação de uma dependência, não exigem notificação.

Para evitar situações em que o upstream discorde do patch, os mantenedores do vcpkg aguardam pelo menos 30 dias após a notificação upstream antes de aplicarem tais patches.

Os mantenedores do vcpkg evitam este período de espera se tiverem alta confiança de que a alteração está correta. Exemplos de patches de alta confiança incluem, mas não estão limitados a:

  • Aceitação do upstream como um patch (por exemplo, retroportação de uma alteração específica de uma solicitação de pull que foi mesclada upstream).
  • A adicionar #includes ausentes.
  • Correções de código de produto pequenas e óbvias (por exemplo, inicializando uma variável não inicializada).
  • Desativando componentes da compilação irrelevantes no vcpkg, como testes ou exemplos.

Bugs identificados pela IA em novos portos

O registo curado pretende evitar enviar código explorável conhecido, mas os mantenedores do vcpkg não conseguem auditar de forma abrangente o código-fonte de todos os componentes. As conclusões da revisão de código por IA não são, por si só, uma determinação final de que o código a montante é vulnerável.

Quando as ferramentas de revisão de código por IA identificam um bug durante a revisão de uma nova porta, os mantenedores do vcpkg exigem que o bug seja reportado ao projeto upstream, seguindo o processo de reporte de segurança do projeto para bugs potencialmente exploráveis. Este requisito aplica-se mesmo quando a constatação diz respeito a código a montante que a porta não corrige.

  • Se o upstream determinar que o comportamento reportado não é um bug, os mantenedores do vcpkg aceitam essa determinação como final e não rejeitam a submissão do novo port com base nessa conclusão.
  • Se o upstream corrigir o bug, os mantenedores do vcpkg aceitam a versão corrigida ou um patch aprovado pelo upstream, sujeito à política de patches e aos outros requisitos para novos ports.
  • Se o upstream não responder dentro de 30 dias após o bug ser reportado, os mantenedores do vcpkg tratam automaticamente a descoberta da IA como um problema explorável e fecham a submissão do novo port.

As exceções de alta confiança ao período de espera do patch não dispensam este requisito de reporte de bugs nem a necessidade de uma resposta do projeto upstream.

Prefira opções em vez de aplicar correções

É preferível definir opções em uma chamada para vcpkg_configure_xyz() em vez de corrigir as configurações diretamente.

Opções comuns que permitem evitar a aplicação de patches:

  • [MSBUILD] <PropertyGroup> configurações dentro do arquivo de projeto podem ser substituídas através de parâmetros /p:
  • [CMAKE] As chamadas para find_package(XYz) nos scripts do CMake podem ser desativadas via -DCMAKE_DISABLE_FIND_PACKAGE_XYz=ON
  • [CMAKE] As variáveis de cache (declaradas como set(VAR "value" CACHE STRING "Documentation") ou option(VAR "Documentation" "Default Value")) podem ser substituídas apenas passando-as na linha de comando como -DVAR:STRING=Foo. Uma exceção notável é se o parâmetro FORCE for passado para set(). Para obter mais informações, consulte a documentação do CMake set

Prefira baixar patches aprovados em vez de verificá-los na porta

Se um patch aprovado ou fundido puder ser obtido a partir de um commit upstream, descarregue-o e aplique-o em vez de o integrar no port. Este processo é preferível porque:

  • Confirma que a upstream aceitou as alterações do patch.
  • Simplifica o processo de revisão ao transferir a responsabilidade a montante.
  • Reduz o tamanho do repositório vcpkg para utilizadores que não estão a usar o patch.
  • Evita conflitos de licença com o repositório vcpkg.

Descarregue patches apenas de URLs fixadas a um ID de commit completo, não de URLs de diff de pull request ou merge request cujo conteúdo possa mudar. Ao descarregar um patch de commit do GitHub ou GitLab, adicione ?full_index=1 à URL. Este parâmetro pede IDs completos de objetos no diff; não torna um URL mutável imutável.

Por exemplo:

https://github.com/linux-audit/audit-userspace/commit/f8e9bc5914d715cdacb2edc938ab339d5094d017.patch?full_index=1

Prefira a aplicação de patches em vez da substituição de valores de VCPKG_<VARIABLE>

Algumas variáveis prefixadas com VCPKG_<VARIABLE> têm um CMAKE_<VARIABLE>equivalente. No entanto, nem todos eles são passados para a compilação do pacote interno (veja a implementação: ferramenta do Windows).

Considere o seguinte exemplo:

set(VCPKG_C_FLAGS "-O2 ${VCPKG_C_FLAGS}")
set(VCPKG_CXX_FLAGS "-O2 ${VCPKG_CXX_FLAGS}")

Usando as cadeias de ferramentas internas do vcpkg, isto funciona, porque o valor de VCPKG_<LANG>_FLAGS é encaminhado para a variável CMAKE_<LANG>_FLAGS correspondente. Mas, uma cadeia de ferramentas personalizada que não reconhece as variáveis do vcpkgnão as encaminhará.

Por causa disso, é preferível corrigir o buildsystem diretamente ao definir CMAKE_<LANG>_FLAGS.

Minimizar correções

Ao fazer alterações numa biblioteca, procure minimizar a diferença final. Isso significa que você não deve reformatar o código-fonte upstream ao fazer alterações que afetem uma região. Ao desativar um condicional, é melhor adicionar um AND FALSE ou && 0 à condição do que excluir todas as linhas do condicional. Se uma região grande precisar ser desativada, é mais simples adicionar if(0) ou #if 0 ao redor da região em vez de excluir todas as linhas do patch.

Não adicione patches se a porta estiver desatualizada e atualizar a porta para uma versão mais recente resolveria o mesmo problema. vcpkg prefere atualizar portas em vez de corrigir versões desatualizadas.

Isso ajuda a manter o tamanho do repositório vcpkg baixo, bem como melhora a probabilidade de que o patch se aplique a versões futuras do código.

Não implementar recursos em patches

O objetivo do patching no vcpkg é permitir a compatibilidade com compiladores, bibliotecas e plataformas. Não se deve implementar novas funcionalidades em detrimento de seguir corretamente o processo de Código Aberto, como submeter um problema ou Pull Request (PR), entre outros.

Não crie testes/docs/exemplos por padrão

Ao enviar uma nova porta, verifique se há opções como BUILD_TESTS ou WITH_TESTS ou POCO_ENABLE_SAMPLES e verifique se os binários adicionais estão desativados. Isso minimiza os tempos de compilação e as dependências para o usuário médio.

Opcionalmente, você pode adicionar um recurso de test que permite a criação dos testes, no entanto, isso não deve estar na lista de Default-Features.

Permitir que os usuários existentes da biblioteca alternem para vcpkg

Não adicionar CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS

A menos que o autor da biblioteca já esteja usando, não devemos usar essa funcionalidade CMake porque ela interage mal com modelos C++ e quebra certos recursos do compilador. As bibliotecas que não fornecem um arquivo .def e não usam declarações __declspec() simplesmente não oferecem suporte a compilações compartilhadas para Windows e devem ser marcadas como tal:

if(VCPKG_TARGET_IS_WINDOWS)
    vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
endif()

Não renomeie binários fora dos nomes fornecidos pelo upstream

Isso significa que, se a biblioteca upstream tiver nomes diferentes em release e debug (libx versus libxd), a biblioteca de depuração não deverá ser renomeada para libx. Vice-versa, se a biblioteca upstream tiver o mesmo nome em release e debug, não devemos introduzir um novo nome.

Ressalva importante:

  • Variantes estáticas e compartilhadas muitas vezes devem ser renomeadas para um esquema comum. Isto permite que os consumidores utilizem um nome comum e ignorem a ligação a jusante. Isto é seguro porque só disponibilizamos um de cada vez.

Se uma biblioteca gera ficheiros de integração do CMake (foo-config.cmake), a renomeação deve ser feita através da modificação direta da compilação do CMake em vez de simplesmente executar file(RENAME) sobre os ficheiros de saída/LIBs.

Finalmente, os arquivos DLL no Windows nunca devem ser renomeados pós-compilação porque quebra as LIBs geradas.

Manifestos

Exigimos que o arquivo de manifesto seja formatado. Use o seguinte comando para formatar todos os arquivos de manifesto:

> vcpkg format-manifest --all

Trigêmeos

Não estamos aceitando pedidos para adicionar trigêmeos não comunitários no momento. A promoção da comunidade para o status de triplete completo baseia-se principalmente no orçamento para o hardware necessário para testar esses tripletes e será impulsionada por métricas enviadas pelo vcpkg para maximizar a probabilidade de que o que as pessoas realmente usam seja totalmente testado.

Adicionaremos trigêmeos comunitários se:

  • Está demonstrado que as pessoas vão realmente usar esse trio comunitário; e,
  • não sabemos que tal trio está quebrado.

Por exemplo, não adicionamos um trio em https://github.com/microsoft/vcpkg/pull/29034 porque o autor estava apenas tentando "completar o conjunto" em vez de indicar que eles realmente usariam tal coisa, e não adicionamos linux-dynamic até que a solução patchelf para tornar os resultados relocáveis fosse criada.

Notas de implementação úteis

Portfiles são executados no modo de script

Enquanto portfile.cmakee CMakeLists.txtcompartilham uma sintaxe comum e construções principais da linguagem CMake (também conhecidas como "Comandos de Script"), os portfiles são executados em "Modo de Script", enquanto os arquivos CMakeLists.txt são executados em "Modo de Projeto". A diferença mais importante entre estes dois modos é que o "Modo Script" não tem os conceitos de "Toolchain", "Language" e "Target". Quaisquer comportamentos, incluindo comandos de script, que dependem dessas construções (por exemplo, CMAKE_CXX_COMPILER, CMAKE_EXECUTABLE_SUFFIX, CMAKE_SYSTEM_NAME) não estarão corretos.

Portfiles têm acesso direto às variáveis definidas no arquivo tríptico, mas CMakeLists.txtnão têm (embora frequentemente ocorra uma tradução -- VCPKG_LIBRARY_LINKAGE versus BUILD_SHARED_LIBS).

Portfiles e compilações de projeto invocadas por portfiles são executadas em processos diferentes. Conceptualmente:

+----------------------------+       +------------------------------------+
| CMake.exe                  |       | CMake.exe                          |
+----------------------------+       +------------------------------------+
| Triplet file               | ====> | Toolchain file                     |
| (x64-windows.cmake)        |       | (scripts/buildsystems/vcpkg.cmake) |
+----------------------------+       +------------------------------------+
| Portfile                   | ====> | CMakeLists.txt                     |
| (ports/foo/portfile.cmake) |       | (buildtrees/../CMakeLists.txt)     |
+----------------------------+       +------------------------------------+

Para determinar o host em um portfile, as variáveis CMake padrão são boas (CMAKE_HOST_WIN32).

Para determinar o destino em um portfile, as variáveis triplete vcpkg devem ser usadas (VCPKG_CMAKE_SYSTEM_NAME).

Consulte a nossa documentação do triplet ,, para uma enumeração completa das configurações possíveis.