È meglio specificare i file di origine con GLOB o ciascun file singolarmente in CMake?


157

CMake offre diversi modi per specificare i file di origine per una destinazione. Uno è usare il globbing ( documentazione ), ad esempio:

FILE(GLOB MY_SRCS dir/*)

Un altro metodo è quello di specificare ogni file singolarmente.

Quale modo è preferito? Il globbing sembra facile, ma ho sentito che ha degli aspetti negativi.

Risposte:


185

Divulgazione completa: originariamente ho preferito l'approccio globbing per la sua semplicità, ma nel corso degli anni sono arrivato a riconoscere che elencare esplicitamente i file è meno soggetto a errori per grandi progetti multi-sviluppatore.

Risposta originale:


I vantaggi del globbing sono:

  • È facile aggiungere nuovi file poiché sono elencati in un solo posto: su disco. Non globbing crea duplicazioni.

  • Il tuo file CMakeLists.txt sarà più breve. Questo è un grande vantaggio se hai molti file. Non alterare fa perdere la logica di CMake tra enormi elenchi di file.

I vantaggi dell'utilizzo di elenchi di file codificati sono:

  • CMake monitorerà correttamente le dipendenze di un nuovo file sul disco: se utilizziamo glob, i file non vengono sottoposti a globbing la prima volta che hai eseguito CMake non verrà rilevato

  • Ti assicuri che vengano aggiunti solo i file che desideri. Globbing può raccogliere file vaganti che non si desidera.

Per aggirare il primo problema, puoi semplicemente "toccare" il CMakeLists.txt che fa il glob, usando il comando touch o scrivendo il file senza modifiche. Ciò costringerà CMake a rieseguire e raccogliere il nuovo file.

Per risolvere il secondo problema, puoi organizzare attentamente il tuo codice in directory, che è probabilmente quello che fai comunque. Nel peggiore dei casi, è possibile utilizzare il list(REMOVE_ITEM)comando per ripulire l'elenco di file distorto:

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

L'unica vera situazione in cui questo può morderti è se stai usando qualcosa come git-bisect per provare versioni precedenti del tuo codice nella stessa directory di build. In tal caso, potrebbe essere necessario pulire e compilare più del necessario per assicurarsi di ottenere i file giusti nell'elenco. Questo è un caso così grave, e uno in cui sei già in punta di piedi, che non è davvero un problema.


1
Anche male con il globbing: i file difftool di git sono archiviati come $ basename. $ Ext. $ Tipo. $ Pid. $ Ext che possono causare errori divertenti quando si tenta di compilare dopo una singola risoluzione di unione.
Mathstuf,

9
Penso che questa risposta ripercuota gli svantaggi di cmake in cui mancano nuovi file, Simply "touch" the CMakeLists.txtva bene se sei lo sviluppatore, ma per gli altri che costruiscono il tuo software può davvero essere un punto dolente che la tua build fallisca dopo l'aggiornamento e l'onere è su di loro per indagare perché.
ideasman42

36
Sai cosa? Da quando ho scritto questa risposta 6 anni fa , ho cambiato idea e ora preferisco elencare esplicitamente i file. L'unico vero svantaggio è "è un po 'più lavoro per aggiungere un file", ma ti fa risparmiare ogni sorta di mal di testa. E per molti versi esplicito è meglio che implicito.
richq

1
@richq Questo hook git ti farebbe riconsiderare la tua posizione attuale? :)
Antonio,

8
Bene, come dice Antonio, sono stati dati i voti per aver sostenuto l'approccio "sconvolgente". Cambiare la natura della risposta è una cosa da fare per quegli elettori. Come compromesso ho aggiunto una modifica per riflettere la mia opinione modificata. Chiedo scusa a Internet per aver causato una simile tempesta in una tazza da tè :-P
richq,

113

Il modo migliore per specificare i file sorgente in CMake è elencarli esplicitamente .

I creatori di CMake stessi consigliano di non usare il globbing.

Vedi: https://cmake.org/cmake/help/v3.15/command/file.html?highlight=glob#file

(Non è consigliabile utilizzare GLOB per raccogliere un elenco di file di origine dall'albero dei sorgenti. Se nessun file CMakeLists.txt cambia quando viene aggiunta o rimossa una fonte, il sistema di generazione generato non può sapere quando chiedere a CMake di rigenerarsi.)

Naturalmente, potresti voler sapere quali sono gli svantaggi: continua a leggere!


Quando Globbing fallisce:

Il grande svantaggio del globbing è che la creazione / eliminazione di file non aggiorna automaticamente il sistema di compilazione.

Se sei la persona che aggiunge i file, questo può sembrare un compromesso accettabile, tuttavia ciò causa problemi ad altre persone che costruiscono il tuo codice, aggiornano il progetto dal controllo della versione, eseguono build, quindi contattano, lamentandoti che
creano il "la build è rotto".

A peggiorare le cose, il fallimento in genere dà qualche errore di collegamento che non fornisce alcun suggerimento per la causa del problema e il tempo viene perso per risolvere il problema.

In un progetto su cui ho lavorato abbiamo iniziato a fare globbing ma abbiamo ricevuto così tante lamentele quando sono stati aggiunti nuovi file, che era una ragione sufficiente per elencare esplicitamente i file invece di globbing.

Ciò interrompe anche i flussi di lavoro comuni di git
( git bisecte il passaggio tra i rami delle caratteristiche).

Quindi non potrei raccomandarlo, i problemi che causa superano di gran lunga la convenienza, quando qualcuno non può costruire il tuo software per questo motivo, potrebbero perdere molto tempo per rintracciare il problema o semplicemente rinunciare.

E un'altra nota, Ricordare di toccare CMakeLists.txtnon è sempre abbastanza, con build automatizzate che usano il globbing, ho dovuto correre cmakeprima di ogni build poiché i file potrebbero essere stati aggiunti / rimossi dall'ultimo edificio *.

Eccezioni alla regola:

Ci sono momenti in cui è preferibile il globbing:

  • Per impostare un CMakeLists.txtfile per progetti esistenti che non usano CMake.
    È un modo veloce per ottenere tutti i sorgenti referenziati (una volta che il sistema di compilazione è in esecuzione - sostituisci globbing con elenchi di file espliciti).
  • Quando CMake non viene utilizzato come sistema di compilazione principale , ad esempio se si utilizza un progetto che non utilizza CMake e si desidera mantenere il proprio sistema di compilazione.
  • Per ogni situazione in cui l'elenco dei file cambia così spesso che diventa poco pratico da mantenere. In questo caso potrebbe essere utile, ma poi devi accettare l'esecuzione cmakeper generare file di build ogni volta per ottenere una build affidabile / corretta (che va contro l'intenzione di CMake - la possibilità di dividere la configurazione dalla costruzione) .

* Sì, avrei potuto scrivere un codice per confrontare l'albero dei file sul disco prima e dopo un aggiornamento, ma questa non è una soluzione così piacevole e qualcosa di meglio lasciato al sistema di compilazione.


9
"Il grande svantaggio del globbing è che la creazione di nuovi file non aggiorna automaticamente il sistema di generazione". Ma non è vero che se non lo fai glob, devi comunque aggiornare manualmente CMakeLists.txt, il che significa che cmake non sta ancora aggiornando automaticamente il sistema di compilazione? Sembra che in entrambi i casi devi ricordarti di fare qualcosa manualmente affinché i nuovi file vengano compilati. Toccare CMakeLists.txt sembra più facile che aprirlo e modificarlo per aggiungere il nuovo file.
Dan

17
@Dan, per il tuo sistema - certo, se sviluppi solo questo va bene, ma per quanto riguarda tutti gli altri che costruiscono il tuo progetto? hai intenzione di inviarli via email per andare e toccare manualmente il file CMake? ogni volta che un file viene aggiunto o rimosso? - La memorizzazione dell'elenco dei file in CMake garantisce che la build utilizzi sempre gli stessi file di cui vcs è a conoscenza. Credimi - questo non è solo un dettaglio sottile - Quando la tua build fallisce per molti sviluppatori - inviano mail list e chiedono su IRC che il codice sia rotto. Nota: (Anche sul tuo sistema potresti tornare indietro nella cronologia di git, ad esempio, e non pensare di entrare e toccare i file CMake)
ideasman42

2
Ah, non avevo pensato a quel caso. Questa è la migliore ragione per cui ho sentito parlare di maltrattamenti. Vorrei che i documenti di Cmake fossero ampliati sul perché raccomandano alle persone di evitare i guai.
Dan

1
Ho pensato alla soluzione di scrivere in data il timestamp dell'ultima esecuzione di cmake. Gli unici problemi sono: 1) probabilmente deve essere fatto da cmake per essere multipiattaforma e quindi in qualche modo dobbiamo evitare che cmake si esegua da solo. 2) Forse più conflitti di unione (che si verificano ancora con l'elenco dei file tra l'altro) In questo caso potrebbero effettivamente essere risolti in modo banale prendendo il timestamp successivo.
Predelnik,

2
@ tim-mb, "Ma sarebbe bello se CMake creasse un file filetree_updated che potevi archiviare, che cambierebbe automaticamente ogni volta che il glob di file veniva aggiornato." - hai appena descritto esattamente cosa fa la mia risposta.
Glen Knowles,

22

In CMake 3.12, i comandi file(GLOB ...)e hannofile(GLOB_RECURSE ...) ottenuto CONFIGURE_DEPENDSun'opzione che ripete cmake se il valore del glob cambia. Dato che quello era lo svantaggio principale del globbing per i file di origine, ora va bene farlo:

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

Tuttavia, alcune persone raccomandano ancora di evitare di perdere tempo per le fonti. Infatti, la documentazione afferma:

Non è consigliabile utilizzare GLOB per raccogliere un elenco di file di origine dall'albero dei sorgenti. ... La CONFIGURE_DEPENDSbandiera potrebbe non funzionare in modo affidabile su tutti i generatori, o se in futuro verrà aggiunto un nuovo generatore che non può supportarlo, i progetti che lo utilizzano verranno bloccati. Anche se CONFIGURE_DEPENDSfunziona in modo affidabile, c'è comunque un costo per eseguire il controllo su ogni ricostruzione.

Personalmente, considero i vantaggi di non dover gestire manualmente l'elenco dei file di origine per superare gli eventuali inconvenienti. Se è necessario tornare ai file elencati manualmente, questo può essere facilmente ottenuto semplicemente stampando l'elenco di fonti globbed e incollandolo nuovamente.


Se il tuo sistema di compilazione esegue un ciclo completo di compilazione e compilazione (elimina la directory di compilazione, esegui cmake da lì e quindi richiama il makefile), a condizione che non eseguano il pull di file indesiderati, sicuramente non ci sono svantaggi nell'utilizzo delle fonti GLOBbed? Nella mia esperienza, la parte di cmake funziona molto più rapidamente rispetto alla build, quindi non è comunque un sovraccarico
Den-Jason

9

Puoi tranquillamente glob (e probabilmente dovrebbe) al costo di un file aggiuntivo per contenere le dipendenze.

Aggiungi funzioni come queste da qualche parte:

# Compare the new contents with the existing file, if it exists and is the 
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of 
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it's the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it's tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

E poi vai a ballare:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

Stai ancora cercando le dipendenze esplicite (e attivando tutte le build automatizzate!) Come prima, solo in due file anziché in uno.

L'unica modifica alla procedura è dopo aver creato un nuovo file. Se non globalmente il flusso di lavoro consiste nel modificare CMakeLists.txt da Visual Studio e ricostruirlo, se lo fai glob esegui esplicitamente cmake - o semplicemente tocca CMakeLists.txt.


All'inizio pensavo che questo fosse uno strumento che aggiorna automaticamente i Makefile quando viene aggiunto un file sorgente, ma ora vedo quale sia il suo valore. Bello! Questo risolve la preoccupazione di qualcuno che si aggiorna dal repository e che ha makedato strani errori di linker.
Cris Luengo,

1
Credo che questo potrebbe essere un buon metodo. Uno ovviamente deve ancora ricordare di attivare cmake dopo aver aggiunto o rimosso un file, ed è anche necessario impegnare questo file di dipendenze, quindi è necessario un po 'di educazione sul lato utente. Il principale svantaggio potrebbe essere che questo file di dipendenze potrebbe originare cattivi conflitti di unione che potrebbero essere difficili da risolvere senza richiedere nuovamente allo sviluppatore di avere una certa comprensione del meccanismo.
Antonio,

1
Questo non funzionerà se il tuo progetto ha incluso in modo condizionale file (ad esempio, alcuni file che vengono utilizzati solo quando una funzione è abilitata o utilizzati solo per un particolare sistema operativo). È abbastanza comune per il software portatile che alcuni file vengono utilizzati solo per piattaforme speciali.
ideasman42

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.