CMake 09: Prepariamo la cartella di rilascio

Nelle puntate precedenti abbiamo visto come creare un applicativo usando CMake per controllare non solo la compilazione, ma anche versione, icona e finestra About. Ora prepariamo il passo finale che ci permetterà di arrivare a creare un Application Bundle o una cartella, con tutte le dipendenze in modo che possa funzionare anche sul computer dell’utente. Ci sarebbe poi un ultimo passo, ovvero la creazione di un installer, ma non ci sono ancora arrivato.

Vi ricordo che tutto il software è disponibile su GitHub.

Deploy di un applicativo

Per fare il deploy dell’eseguibile servono 2 step:

  1. Copiare il target, ovvero il risultato della compilazione in una cartella di rilascio
  2. Aggiungere tutte le librerie e le dipendenze necessarie usando uno dei comandi:

Inoltre dobbiamo implementare uno switch per fare queste operazioni solo se stiamo compilando in release, altrimenti sono una perdita di tempo.

In questa prima parte vedremo come implementare lo switch e come creare la cartella di rilascio, per le varie piattaforme.

Nella seconda parte vedremo come copiare il file compilato e invocare i comandi macdeployqt, windeployqt.

Filtrare il caso Release

Come faccio a creare in CMakeLists.txt delle istruzioni che vanno eseguite solo in release?

Semplice, si usa la funzione if(), seguita da MATCHES e da una regular expression, sulla variabile CMAKE_BUILD_TYPE

if(CMAKE_BUILD_TYPE MATCHES "Release" )
    ...
endif()

Questa è una applicazione semplificata delle regular expression, in cui il match è valido se CMAKE_BUILD_TYPE contiene la stringa “Release”, non se è esattamente uguale alla stringa “Release”

Per chiudere un if() si deve mettere endif().

Creare la cartella di rilascio

Come vogliamo chiamare la cartella in cui mettiamo l’applicativo rilasciato?

Direi che si deve creare una cartella per ogni piattaforma, combinando nel nome varie informazioni, come per esempio il nome del progetto, la piattaforma (macOS, Linux, Win), l’architettura supportata (32 o 64 bit), la versione di Qt usata.

Ora la domanda che mi sono posto è la seguente: “perché dovrei inventarmi una sintassi quando questo problema è già stato affrontato e risolto da TheQtCompany?”

Vediamo in dettaglio che cartelle vengono create quando compilo un progetto con Qt. Nel caso macOS ottengo le seguenti cartelle:

Nel caso Linux ottengo le seguenti cartelle

Nel caso Windows 10 ottengo il doppio delle cartelle dato che ho sia il caso 32 bit che il caso 64 bit:

Il nome tale e quale non mi piace… voglio togliere build, e Release, dato che non sono interessato ad un rilascio di Debug. Inoltre la parola Desktop può avere senso per Qt Creator, ma nel nostro caso sarebbe meglio sostituirla con il nome della piattaforma, ovvero macOS, Linux, Windows, mentre lascerei inalterato:

  • Il nome dell’applicazione
  • La versione della libreria Qt
  • Il compilatore
  • La versione della piattaforma (32 bit o 64 bit)

E la versione dell’applicazione? Quella la metterei direttamente nel nome dello “application bundle” o in una sottocartella. In questo modo, posso archiviare versioni diverse dello stesso applicativo, nella cartella relativa alla piattaforma.

Dopo tutte queste riflessioni ecco la struttura che voglio generare:

CmakeWidgetProject-macOS-Qt-5.15-0-clang-64bit/
	CMakeWidgetProject-1.2.3.4.app

CmakeWidgetProject-Linux-Qt-5.15.0-GCC-64bit/
	CmakeWidgetProject-1.2.3.4/

CmakeWidgetProject-Win-Qt-5.15-0-MSVC2019-32bit/
	CmakeWidgetProject-1.2.3.4/

CmakeWidgetProject-Win-Qt-5.15.0-MSVC2019-64bit/
	CMakeWidgetProject-1.2.3.4/

Calcolo dei vari percorsi: BUILD_PACK

Vediamo come calcolare la parte finale della cartella principale, ovvero:

Qt-5.15-0-clang-64bit
Qt-5.15.0-GCC-64bit
Qt-5.15-0-MSVC2019-32bit
Qt-5.15.0-MSVC2019-64bit

Inizialmente avevo implementato questa parte usando la cartella di output, il che avrebbe semplificato notevolmente il codice.

Ma c’era un piccolo problema: “La cartella di Output esiste solo DOPO la compilazione”.

Il risultato era che dovevo compilare 2 volte in release per essere sicuro che il tutto funzionasse. Ho risolto la cosa accedendo al percorso di qmake in quanto contiene tutte le informazioni, anche se non tutte sono così immediatamente usabili.

Partiamo dal percorso dell’applicativo qmake, disponibile come proprietà del target:

get_target_property(QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)

Il risultato di questa interrogazione, nel mio caso (macOS) è la stringa:

/Users/QtQb/Qt/5.15.0/clang_64/bin/qmake

Ora usando la funzione string() e una regular expression estraiamo il pezzo che va da Qt fino a bin:

string(REGEX MATCH "Qt/.+bin" BUILD_PACK_01 ${QMAKE_EXECUTABLE})

Il risultato della funzione string() è il seguente:

Qt/5.15.0/clang_64/bin

Ci siamo quasi… Solo che controllando i risultati delle varie piattaforme, mi sono accorto che nel caso Windows, il percorso di qmake non contiene 32 nel caso 32 bit!

Quindi devo gestire i due casi in modo separato, ovvero, se trovo un match con “_64/bin” allora metto “_64bit”, altrimenti, cerco “/bin” e lo sostituisco con “_32bit”:

    string(REPLACE "_64/bin" "_64bit" BUILD_PACK_02 ${BUILD_PACK_01})
    string(REPLACE "/bin" "_32bit" BUILD_PACK_03 ${BUILD_PACK_02})

Ora non mi resta che convertire il percorso in una stringa, sostituendo il carattere “/” con “-”

    string(REPLACE "/" "-" BUILD_PACK ${BUILD_PACK_03})

Nel caso macOS, BUILD_PACK vale:

Qt-5.15.0-clang_64

Completare il percorso base

Partiamo dal percorso del codice sorgente del progetto e saliamo di un livello in modo da mettere la cartella di rilascio a fianco della cartella con i sorgenti:

set(BASE_DEPLOY_FOLDER "${PROJECT_SOURCE_DIR}/../")

Poi, a seconda della piattaforma combiniamo gli elementi: PROJECT_NAME, nome della piattaforma e BUILD_PACK. Nel caso APPLE voglio usare macOS

    if(APPLE)
        string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} ${PROJECT_NAME}
            "-macOS-" ${BUILD_PACK} "/")

Nel caso Linux, il test va fatto usando UNIX e nel nome voglio mettere:

-Linux-

    elseif(UNIX)
        string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} ${PROJECT_NAME} 
            "-Linux-" ${BUILD_PACK} "/")

Nel caso Windows, faccio il test su WIN32, che ovviamente vale anche per WIN64 e poi inserisco “-Win-”

 elseif(WIN32)
        string(APPEND DEPLOY_FOLDER ${BASE_DEPLOY_FOLDER} ${PROJECT_NAME}  
            "-Win-" ${BUILD_PACK} "/")

Calcolare DEPLOY_FOLDER_BUNDLE

A questo punto posso calcolare la vera cartella di destinazione o lo “application bundle” nel caso macOS. Partendo da quest’ultimo:

        string(APPEND DEPLOY_FOLDER_BUNDLE ${DEPLOY_FOLDER} "/" 
            ${PROJECT_NAME} "-" ${PROJECT_VERSION} ".app")

Nel caso UNIX e WIN32 questo è il codice da aggiungere:

        string(APPEND DEPLOY_FOLDER_BUNDLE ${DEPLOY_FOLDER} "/"
            ${PROJECT_NAME} "-" ${PROJECT_VERSION})

Creare la cartella

Finalmente posso creare la cartella di destinazione, usando il comando:

    make_directory(${DEPLOY_FOLDER_BUNDLE})

Per testare che il tutto funzioni, dobbiamo unire i vari pezzi. Il codice risultante è abbastanza complicato dato che in gran parte viene ripetuto per le 3 piattaforme:

if(CMAKE_BUILD_TYPE MATCHES "Release")
    get_target_property(QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)
    string(REGEX MATCH "Qt/.+bin" BUILD_PACK_01 ${QMAKE_EXECUTABLE})
    string(REPLACE "_64/bin" "_64bit" BUILD_PACK_02 ${BUILD_PACK_01})
    string(REPLACE "/bin" "_32bit" BUILD_PACK_03 ${BUILD_PACK_02})
    string(REPLACE "/" "-" BUILD_PACK ${BUILD_PACK_03})
    set(BASE_DEPLOY_FOLDER "${PROJECT_SOURCE_DIR}/../")
    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")
    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})
    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})
    endif()
    make_directory(${DEPLOY_FOLDER_BUNDLE})
endif()

Se tutto funziona, e la configurazione corrente è quella di release, basta salvare CmakeLists.txt perchè il calcolo della cartella si attivi e la cartella venga creata. Ecco il risultato finale con tutte le varie piattaforme:

Conclusioni

Questo post all’apparenza è molto semplice, ma laborioso a causa delle sottili differenze tra le piattaforme. Una piattaforma vuole la cartella con estensione .app, l’altra vuole 32 e 64 bit. Inoltre trovare le informazioni in CMake non è sempre immediato. Servirebbe un debugger, un tool con il quale andare a vedere tutte le variabili interne in modo da non dover fare 100 prove prima di trovare quella giusta.

Ad ogni buon conto, anche questa parte è fatta! Nel prossimo articolo vedremo come usare i tool di deploy per recuperare tutte le dipendenze.

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 *