CMake 10: Deploy Qt

Eccoci finalmente all’episodio finale della saga CMake is the Future. Tempo di impacchettare il lavoro fatto e provare ad usarlo sul PC del cliente!

Nella puntata precedente abbiamo creato la cartella per il rilascio, ora è tempo di riempirla.

Vi ricordo che su GitHub trovate tutto il codice dell’esempio.

Copiare l’applicazione

Vediamo ora come sia possibile copiare l’applicativo appena compilato nella cartella che abbiamo creato.

Il meccanismo che sfrutteremo è quello di aggiungere un “custom command” che si attiva quando la compilazione è stata completata. Immaginate il custom command come una “lambda expression”, che viene definita in un punto del codice e si attiva quando avviene l’evento.

Dal momento che ci servono 3 comandi diversi per le 3 piattaforme, prepareremo questi comandi all’interno dell’albero:

    if(APPLE)
        
    elseif(UNIX)
    
    elseif(WIN32)
    
    endif()

E siccome l’esecuzione del codice non è sequenziale, ma basata sugli eventi, possiamo metterlo nell’albero che abbiamo appena creato. Il che sembrerà un poco strano dato che sembrerà di fare la copia prima di aver creato lo “Application Bundle” o la cartella.

Copiare l’application bundle: macOS

Le applicazioni per macOS sono distribuite in cartelle speciali dette “application bundle” con una certa struttura, in cui viene messo l’eseguibile, l’icona, le info relative all’applicazione, le librerie e le risorse.

Per fare la copia serve un comando custom da attivare dopo la compilazione, specifico per il macOS, che faccia la copia della cartella. Pertanto, espando la sezione if(APPLE) aggiungendo il comando copy_directory. Ecco come il blocco diventa:

if(APPLE)
    string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} 
        ${PROJECT_NAME} "-macOS-" ${BUILD_PACK} "/")
    string(APPEND DEPLOY_FOLDER_BUNDLE ${DEPLOY_FOLDER} "/"
        ${PROJECT_NAME} "-" ${PROJECT_VERSION} ".app")
    add_custom_command(TARGET ${CMAKE_PROJECT_NAME} 
        POST_BUILD COMMAND ${CMAKE_COMMAND} -E
        copy_directory $<TARGET_BUNDLE_DIR:${CMAKE_PROJECT_NAME}>
        ${DEPLOY_FOLDER_BUNDLE}
        )

Se vi state chiedendo come funziona add_custom_command() fate bene, non si capisce molto. In pratica viene invocato il comando CMake (e già qui ci sarebbe da dire dato che CMake chiama CMake) con opzione -E che lo trasforma in una sorta di shell. Poi gli si passa copy_directory(), che non è un comando di macOS, ma della shell di CMake.

La cartella di partenza è presa dalla lista TARGET_BUNDLE_DIR, per il caso del nostro progetto. Sembra un non senso solo perché noi abbiamo un solo progetto, ma potrebbero esserci più target dove ognuno genera il suo bundle.

Penso sia invece importante far notare che la cartella di destinazione è quella che noi abbiamo calcolato, ovvero la DEPLOY_FOLDER_BUNDLE.

Attenzione: questo comando viene eseguito solo dopo la compilazione, pertanto per controllare se funziona dovete lanciare un rebuild del progetto.

Ecco il risultato finale! Notate che ora lo “application bundle” visualizza l’icona dell’applicazione.

Copiare l’applicativo: Linux

Il caso Linux è più semplice del caso macOS dato che devo copiare solo il file eseguibile, non una cartella. Pertanto completiamo il ramo Linux con un add_custom_command() in questo modo:

elseif(UNIX)
    string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} 
        ${PROJECT_NAME} "-Linux-" ${BUILD_PACK} "/")
    string(APPEND DEPLOY_FOLDER_BUNDLE ${DEPLOY_FOLDER} "/"
        ${PROJECT_NAME} "-" ${PROJECT_VERSION})
    
    add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E
        copy $<TARGET_FILE:${CMAKE_PROJECT_NAME}> 
        ${DEPLOY_FOLDER_BUNDLE}
        )

In pratica chiediamo a CMake, invocato con l’opzione -E di copiare il target ovvero il risultante della compilazione, nella cartella DEPLOY_FOLDER_BUNDLE usando il comando copy, che di nuovo non è un comando del sistema operativo, ma della shell di CMake.

Copiare l’applicativo: Windows

Ora per finire, aggiungiamo al caso WIN32 il comando custom per fare la copia dell’eseguibile, in modo analogo a quanto fatto per il caso Linux:

elseif(WIN32)
    string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} 
        ${PROJECT_NAME} "-Win-" ${BUILD_PACK} "/")
    string(APPEND DEPLOY_FOLDER_BUNDLE ${DEPLOY_FOLDER} "/"
        ${PROJECT_NAME} "-" ${PROJECT_VERSION})
    
    add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E
        copy $<TARGET_FILE:${CMAKE_PROJECT_NAME}>
        "${DEPLOY_FOLDER_BUNDLE}"
        )
endif()

Impacchettare le dipendenze

Come faccio ad impacchettare l’eseguibile e le sue dipendenze in una cartella in modo da poter fare un rilascio che possa funzionare su qualsiasi computer?

Qt fornisce un comando specifico per macOS e uno per windows, che prende il nostro compilato, lo analizza, scopre le dipendenze e copia tutti i file necessari, ovvero le librerie e le risorse, all’interno della stessa cartella.

I due comandi si chiamano:

E il caso Linux? Purtroppo non c’è un comando ufficiale, ma ci sono soluzioni simili open-source, che al momento non ho ancora testato.

Vediamo ora cosa serve per chiamare questi due comandi.

Cercare il comando di deploy

Abbiamo già trovato la cartella in cui si trova qmake quando abbiamo calcolato il percorso di rilascio.

get_target_property(QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)

Ora, dal percorso QMAKE_EXECUTABLE estraiamo il percorso della cartella:

get_filename_component(QT_BIN_DIR "${QMAKE_EXECUTABLE}" DIRECTORY)

A questo punto abbiamo QT_BIN_DIR ed in questa cartella andiamo a cercare uno dei due programmi: windeployqt oppure macdeployqt

find_program(DEPLOYQT_EXECUTABLE windeployqt HINTS "${QT_BIN_DIR}")
find_program(DEPLOYQT_EXECUTABLE macdeployqt HINTS "${QT_BIN_DIR}")

Per come funziona find_program(), se uno delle due invocazioni trova il programma, la variabile viene impostata, altrimenti non viene modificata. Pertanto le due righe di codice sopra valgono per tutti e due i sistemi operativi e non serve mettere alcuno test per la variabile APPLE o WIN32.

E naturalmente nel caso Linux, la variabile resta non definita, pertanto posso controllare che sia valida in questo modo:

    if(DEPLOYQT_EXECUTABLE)
    endif()

Eseguire il comando custom (macOS e Windows)

A questo punto non resta che inserire un’altro comando custom dentro il blocco if() che viene eseguito in release:

if(CMAKE_BUILD_TYPE MATCHES "Release" )

    ...

    if(DEPLOYQT_EXECUTABLE)
        add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
            COMMAND "${CMAKE_COMMAND}" -E
            env "${DEPLOYQT_EXECUTABLE}" "${DEPLOY_FOLDER_BUNDLE}"
            )
    endif()

endif()

In pratica questo comando dice che:

  • dopo che la build del target CMAKE_PROJECT_NAME è stata completata
  • esegui un comando con COMMAND
  • esegui CMake come shell (opzione -E)
  • grazie all’opzione env esegui il comando DEPLOYQT_EXECUTABLE
  • passa a macdeployqt o windeployqt il percorso DEPLOY_FOLDER_BUNDLE.

Il risultato è che nel “Application Bundle”o nella cartella vengono copiate tutte le dipendenze necessarie al funzionamento dell’applicazione.

Dato che DEPLOYQT_EXECUTABLE non è disponibile per linux, il test

if(DEPLOYQT_EXECUTABLE)

serve per evitare che si generino errori durante la compilazione.

Application Bundle per macOS

Ecco come diventa l’application bundle dopo avere eseguito macdeployqt.

Cartella rilascio per Windows

Ecco come diventa la cartella dopo avere eseguito windeployqt:

Come si vede, tutte le librerie di Qt necessarie sono state copiate. Ora basta copiare la cartella su un qualsiasi PC e la nostra applicazione sarà utilizzabile. Qualora non funzionasse, basterà lanciare vc_redistx86 per installare eventuali librerie ridistribuibili di Visual Studio.

Conclusioni

La serie su CMake, dopo ben 10 articoli, si avvia a conclusione. Devo dire che quando ho iniziato ad affrontare la materia non mi aspettavo fosse così complessa. Sono d’accordo con chi dice che forse possiamo sperare in qualcosa di meglio di CMake, ma per il momento questo è.

Ci sono vari tentativi di semplificazione modernizzazione, che vanno sotto il nome di “Modern CMake” e alcuni concetti li ho seguiti, ma a causa dell’età del prodotto, penso che ci siano molte stratificazioni sovrapposte, alcune in contrapposizione che rendono complicato l’uso senza una buona guida.

Ora vorrei provare a convertire quanto fatto in un template in modo da non dover re-inventare la ruota tutte le volte… vedremo.

Newsletter QtQB

Se vuoi ricevere i miei post e le mie notizie via mail, iscriviti!

Autore: Gianbattista

Appassionato di tecnologia, sono l'autore di Qt5 Quanto Basta. Per lavoro mi occupo di elaborazione delle immagini per applicazioni industriali, ma nel tempo libero adoro creare applicazioni con Qt (www.qt.io)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *