openSUSE:Build condizionali RPM

(Reindirizzamento da openSUSE:RPM conditional builds)
Questa pagina spiega cosa sono i condizionali RPM e come usarli quando si creano pacchetti per openSUSE.
Icon-warning.png
Attenzione! Parte del contenuto di questo articolo è basato su regole indotte dalla pratica, dato che non sempre sono ben documentate. Va dunque usato con una certa attenzione.


Significato dei condizionali e ragioni per utilizzarli

I pacchetti sono in grado di fornire supporto per funzionalità di terze parti, a pagamento (commerciali) o poco diffuse, passando al comando rpmbuild opzioni booleane condizionali, ovvero switch con sintassi del tipo: --with(out) "condizione". rpmbuild è il comando che sta alla base del sistema di creazione dei pacchetti RPM, il comando osc build che si usa abitualmente è in realtà una macro che punta a rpmbuild.

Nel caso dei tool automatici propri del sistema di build, fare uso dei condizionali è come utilizzare la nota sintassi configure --with-gui presente in svariati casi d'uso di configure. Nel caso di CMake equivale ad aggiungere la definizione per il compilatore (passata al preprocessore) -DENABLE_GTK3_MODULE, o una simile. Tali parametri --with-gui si possono implementare modificando direttamente il codice nel file spec (ogni volta che serve), oppure definendo alcune macro booleane.

I condizionali sono quindi proprio quegli operatori booleani che si è andati a definire. Per usare i condizionali è necessario definire la condizione nel preambolo del file spec, modificando poi, a seconda delle esigenze e in modo opportuno, i tag BuildRequires, Requires, Provides, Obsoletes, oppure i sotto-pacchetti indotti da %package (-n) nonché sezioni %files del file spec.

Condizionali a confronto con modifica diretta del codice

Il vantaggio dell'usare i condizionali consiste nella possibilità di controllare il loro stato di attivazione o disattivazione semplicemente mediante istruzioni perfettamente valide nei file spec, mentre se si sceglie di modificare direttamente il codice dei file spec, l'unico modo per commutare lo stato di applicazione è di commentare le parti interessate (istruzioni e relativi commenti) con #. Le macro booleane consentono poi di regolare il comportamento dell'intero file spec, soltanto grazie alla modifica di un numero che può essere o 0 o 1. Si può anche lasciare invariato il file spec e semplicemente fornire i parametri corretti al comando per modificarne il comportamento. Invece, con la modifica diretta del file spec, è necessarie modificare a mano, ogni volta che si vuole attivare lo "switch", svariate parti del file spec (e non solo uno o più valori booleani), per cui è facile commettere errori che impediscono di compilare il pacchetto.

Semplicità nell'installare i pacchetti

Se invece di usare i condizionali nella sezione %build, si usasse --with-gui, scritto a mano nel file spec, sarebbe allora necessario aggiungere BuildRequires: gtk2-devel nel preambolo di tale file. In tal modo il file RPM risultante sarebbe dotato, in maniera predefinita, del supporto per l'interfaccia grafica (gui). E tale supporto non potrebbe essere eliminato direttamente dal pacchetto in fase di creazione dello stesso, tranne che facendo ricorso a qualche espediente, come aggiungere il sotto-pacchetto *-gui, ma con l'accortezza di non segnarlo come richiesto (con "Requires") da altri sotto-pacchetti o dal pacchetto principale. Il risvolto negativo di questa soluzione è che, sebbene venga comunque offerta all'utente finale la possibilità di scegliere, ci sono buone probabilità che quest'ultimo non sia a conoscenza della possibilità di selezionare il pacchetto aggiuntivo (quello per la gui, nell'esempio). Ad esempio per via di:

  • Strutture dei nomi diverse

se un utente vuole installare pidgin e vuole che il programma sia dotato di supporto per la riga di comando, e non sa che l'interfaccia a riga di comando di pidgin si chiama finch, potrebbe non trovare mai che quel particolare programma a riga di comando. E se eseguisse sudo zypper in pidgin, e il risolutore automatico delle dipendenze non selezionasse finch, potrebbe anche non accorgersene del tutto.

  • Pacchetti orfani

nel casi in cui l'utente usi un qualche strumento per rilevare ed eliminare automaticamente i pacchetti orfani, come rpmorphan o YaST (ma possibilmente con risultati diversi), potrebbe accidentalmente eliminare il sotto-pacchetto (giacché non richiesto da altri pacchetti).

Ma se si usano le istruzioni di build condizionali, anche se si impostano i condizionali come attivi (valore "true" o 1) , qualora l'utente installi il pacchetto mentre i prerequisiti non siano soddisfatti, per esempio perché manca libgtk2, il gestore di pacchetti RPM semplicemente annullerà l'installazione dei sotto-pacchetti relativi all'interfaccia grafica, mentre installerà il pacchetto principale, tranne il caso in cui sia stata esplicitamente dichiarata la richiesta, con un "Requires", del sotto-pacchetto *-gui, invece che soltanto raccomandarlo con "Recommends".

Come accortamente evitare la blacklist del Build Service

Talvolta è inevitabile usare i condizionali invece che modificare a mano il file spec, dato che altrimenti l'applicazione non potrebbe essere ammessa nel Build Service. Gli esempi abbondano nell'ambito dei lettori multimediali, ad esempio: clementine.

Clementine fornisce il servizio di musica online in streaming Spotify (solo per gli utenti paganti negli USA) per mezzo di una libreria commerciale, ma utilizzabile gratuitamente, che si chiama libspotify. Ma su OBS non può essere ospitato nulla soggetto a brevetti. Per cui quella libreria non può essere semplicemente aggiunta nel file spec (l'operazione fallirebbe con l'errore "unresolvable" per libspotify).

In queste circostanze, con i condizionali è possibile prevedere un'operazione di "creazione alternativa di pacchetti" e accontentare gli utenti che desiderano prima di tutto la versione completa del programma. Non dovranno compilarsi localmente il pacchetto libspotify16, installarlo con rpm -ivh assieme al relativo pacchetto -devel, e quindi aggiungere BuildRequires: libspotify-devel al preambolo del file spec per il pacchetto, e modificare la sezione %files, per compilare la versione completa.

Che crea pacchetti ha dunque la possibilità di aggiungere condizionali e BuildRequires per il file %{_libdir}/libspotify.pc al preambolo del proprio file spec, e poi fornire, come opzione, i file nella sezione %files. Gli utenti possono allora usare semplicemente la catena configure && make && sudo make install per installare libspotify, e poi lanciare sudo rpmbuild --rebuild --with libspotify per installare la versione completa senza la necessità di dover conoscere le regole sintattiche per i file spec, necessarie per aggiungere source.tar.bz2 (e le patch) in SOURCES, e il file spec in SPECS, al termine di cui potrebbero eseguire sudo rpmbuild -ba clementine.spec. Potranno invece semplicemente ottenere il file RPM risultante senza dover conoscere le complessità della creazione dei pacchetti.

In questo modo è possibile combinare le installazioni tradizionali con RPM, per permettere agli utenti alle prime armi di risparmiare tempo e fatica.

Pacchetti per distribuzioni diverse (multi-distribuzione)

Talvolta, a causa di differenze tra le varie distribuzioni, la situazione di alcune dipendenze può risultare che queste "non sono indispensabili ma non fa male tenerle". Di solito, per risolvere il problema, si usa:

%if 0%{?suse_version} <= 1220 && 0%{?suse_version} > 1130
# Osservazione: si tratta di macro in parallelo, non macro che girano su più thread. Le macro non usano il multi-thread.
BuildRequires: gtk2-devel
%endif

Ma ci si imbatte in una limitazione: questo metodo punta ad assicurare la compatibilità tra metodi diversi tra le distribuzioni di assegnare i nomi ai pacchetti, non è pensato per avere a che fare con i pacchetti "in forse".

Per esempio, di certo non si può avere Requires: libktorrent3 in openSUSE 12.1, dato che deve essere libktorrent4; ma la situazione cambia con il riconoscimento facciale di digikam, che fa uso di libkface. libkface non è mai stata rinominata, ma in distribuzioni precedenti la openSUSE 11.4 è instabile, per cui la si rimuove. Ma se un utente poi la installa comunque, non succede nulla di irreparabile. Così è possibile definire un condizionale with_libkface e impostarlo a 0 per versioni inferiori alla 11.4. Di conseguenza YaST non renderà disponibile questa funzionalità in modo automatico, ma se la si vuole avere, si potrà ricorrere al comando rpmbuild --rebuild --with libkface.

Un altro esempio, diverso, è il supporto per mono in pidgin. Questo non è necessario per rilasci successivi ad openSUSE 11.4, dal momento che ci sono pochi plugin in mono, e il supporto per quest'ultimo renderà il programma principale instabile, inoltre richiederebbe così tante dipendenze che si è deciso di rimuoverlo. Ma dato che comunque nella 11.3 il supporto era presente, sarebbe stato prematuro toglierlo definitivamente agli utenti, per cui si è impostato un condizionale with_mono e lo si è attivato per 11.4 e precedenti rilasci. Ma se si vuole usare mono per scrivere plugin in .net per pidgin su versioni di openSUSE successive alla 11.4, sarà sufficiente lanciare un rebuild.

Esempi d'uso dei condizionali

Prima di applicare queste istruzioni, potrebbe essere utile consultare la sezione "Conditional build stuff." nel file /usr/lib/rpm/macros della propria openSUSE installata.

È necessario dapprima aggiungere la riga con %define nel preambolo del file spec che si sta modificando:

%bcond_with video 1 
# Qui la condizione è video; è preimpostata su off e la si attiva da riga di comando
# con lo switch --with video. Cioè la si deve impostare a 1, in modo predefinito non è definita.

Qui la macro %bcond_with() inizializza la global with_video a 1, se si crea il pacchetto con --with video</vide> (oppure se <code>_with_video è già stata definita nel file spec, naturalmente).

aggiungere poi queste righe per la sezione BuildRequires:

%if %{with video}
BuildRequires: v4l2-devel
%endif

Qui, se with_video esiste, %with() viene espansa a 1, altrimenti a 0.

Inoltre, dato che si vuole generare un altro sotto-pacchetto:

%if %{with video}
%package video or %package -n libfoo-video
Summary: video plugin for package foo
Group: System/GUI/KDE
Provides: foo-visual = %{version}
Obsoletes: foo-visual < %{version}
%description video
blabla
%endif

e quindi, nella sezione %build aggiungere:

%configure \
       %{?_with_video} \
       --with-gui # Qui la condizione è stata scritta manualmente nel file spec, dato che si è certi che questo programma sarebbe inutilizzabile senza gui

oppure aggiungere:

cmake -DCMAKE_INSTALL_PREFIX=%{_prefix} \
%if 0%{with video}
      -DENABLE_VIDEO \
%endif
      ..

per configurare la compilazione.

Nella sezione %files si userà:

%files -n %{name}.lang
%defattr(-,root,root)
..
%if 0%{with video}
%{_libdir}/%{name}/libfoo-video.so.1.2.3
%dir %{_datadir}/%{name}/foo-video/
%{_datadir}/%{name}/foo-video/example 
(In luogo delle due righe qui sopra si può usare %{_datadir}/%{name}/foo-video/)
%dir %{_sysconfdir}/%{name}/
%config %{_sysconfdir}/%{name}/%{name}-video.conf
(Osservazione: sia per il pacchetto principale che per il sotto-pacchetto è necessario specificare che al particolare pacchetto appartenga la directory %{_sysconfdir}/%{name})
%endif
..

Oppure:

%if 0%{with video}
%files -n libfoo-video
%defattr(-,root,root)
..
%endif

A questo punto i build condizionali sono finalmente impostati.

Non serve passare alcun parametro alle macro %make o %make_install, ad esempio fornendo loro le %{optflags}. Le impostazioni predefinite sono già adeguate. Se si incontreranno delle dipendenze da soddisfare (che si erano in precedenza attivate), verranno automaticamente passati i necessari parametri di compilazione. Il contenuto dei file RPM risultanti è basato sulla configurazione predefinita. Tuttavia il pacchetto RPM sorgente (.srpm) supporta i parametri con prefisso --with(out), ovvero per guidare la configurazione si usano i condizionali al posto del solo comando di build, il quale si limiterà a processare quanto gli viene passato.

Un'altra importante osservazione da tenere presente riguardo i condizionali è che non si applica il ragionamento secondo cui, se si è definito un "%bcond_with", allora sarà anche disponibile "%bcond_without". In realtà sono due condizionali completamente diversi. Ovvero, se è stato dichiarato un --with, e ora si vuole usare il --without, il modo corretto di farlo è di non aggiungere nulla oltre a quanto impostato prima, poiché se si definisce un --with, il suo valore predefinito equivale ad avere without. Poiché, omettendo --with foo<code> dal comando, per via del comportamento di <code>%bcond_with(), %{with foo} verrà espansa a 0 (a meno che sia stata definita a mano with_foo a qualche valore, naturalmente).

Variazioni nell'uso dei condizionali

Anche coloro che creano pacchetti per openSUSE fanno comunemente uso di alcuni variazioni nell'uso dei condizionali, come i condizionali creati a partire da %define in pidgin:

%define with_mono 1
%if %with_mono
%endif

Questa variazione è implementata nel modo convenzionale di definire una macro RPM:

%define something 0/1.

Con la differenza che qui "something" può essere soltanto una variabile booleana, e valere o 0 o 1. Successivamente quel condizionale verrà usato nell'istruzione %if something.

Una seconda variazione si ottiene definendo %with_something 0, per poi usare:

%if %{with_something} 
do something p.es.: --with-video
%endif

Inoltre si ricorda l'uso di condizionali senza dichiarazione o definizione, ad esempio nel pacchetto gegl:

%if 0%{?BUILD_ORIG}
%if 0%{?BUILD_ORIG_ADDON}
...
%endif
%endif

Questa variazione usa una sintassi che combina insieme definizione e utilizzo del build condizionale: 0 indica il valore booleano assegnato di default, ? significa "dipende" da come espande l'espressione che lo segue. Per cui tutta quanta l'espressione significa "se la condizione esiste BUILD_ORIG è soddisfatta, allora imposta a 1 lo stato per il contenuto circondato dagli if, ciò significa che se ne eseguirà il build; se non è presente allora usa il valore di default, cioè 0, e quindi non eseguire il build annesso.

Tuttavia l'uso di queste tre variazioni dei build condizionali non è molto consigliato in quei pacchetti potenzialmente indirizzati agli utenti finali (gegl è una libreria da cui dipende gimp, gli utenti finali non la compileranno, se lo facessero allora non sarebbero più utenti finali, dato che la compilazione di questa è molto difficile da mettere a punto), poiché sarà causa di problemi per essi. Per ri-creare il pacchetto si dovrà usare:

sudo rpmbuild -D 'BUILD_ORIG 1'

Di solito queste variazioni dei build condizionali vengono usate per far dipendere il comportamento dall'ambiente di build, piuttosto che dalle opzioni disponibili all'utente.

Infine, come forse sarà già noto, macro come %{?suse_version} o %{?fedora_version} sono tutte condizionali, ma condizionali preimpostati da openSUSE, risultano più complessi e sono integrati in Open Build Service.