openSUSE:Packaging guidelines

Jump to: navigation, search
The Packaging guidelines regulate all the nitty gritty details of packaging for the openSUSE distribution. There are general rules, rules about legal aspects of a package, about specific package features and about specific package types.
  • It is the reviewer's responsibility to point out specific problems with a package and a packager's responsibility to deal with those issues. The reviewer and packager work together to determine the severity of the issues — whether they block a package or can be worked on after the package is in the repository.
  • The packaging guidelines are a collection of common issues and the severity that should be placed on them. While these guidelines should not be ignored, they should also not be blindly followed. If you think that your package should be exempt from part of the guidelines, please bring the issue to the openSUSE-packaging mailing list.
  • Please also note that, in the Build Service, a lot of the rules will be enforced, or warned about, after a successful build, by a tool called rpmlint. The packager should always check its output to be notified of common packaging errors and to get hints where the packaging could be improved. See Packaging checks for explanations about most of the rpmlint warnings. That page also contains instructions how false positives can be suppressed.
  • If there is a need to change the guidelines please follow the change process as outlined.

General guidelines

There are some general rules that must be followed.

No inclusion of pre-built binaries or libraries

All binaries or libraries included with openSUSE packages must have been built from source code included in the source package. This is a requirement for the following reasons:

  • Security: Pre-packaged binaries and libraries not built from source could include anything, malicious or dangerous content, or just being plain broken. Also, these are functionally impossible to patch and bugfix.
  • Compiler flags: Pre-packaged binaries and libraries not built from source probably do not have the standard openSUSE compiler flags for security and optimization.

If you are in doubt as to whether something is considered a binary or library, here is some helpful criteria:

  • Is it executable? If so, it is probably a binary.
  • Does it contain a .so, .so.#, .so.#.# or .so.#.#.# extension? If so, it is probably a library.
  • If in doubt, ask on the openSUSE-packaging mailing list.

Packages which require non-open source components to build are also not permitted (e.g. proprietary compiler required).


  • Some software (usually related to compilers or cross-compiler environments) cannot be built without the use of a previous toolchain or development environment (open source). If you have a package which meets this criteria, contact the openSUSE Packaging Committee for approval.
  • An exception is made for binary firmware, as long as it meets the documented requirements: BinaryFirmware

Bundling of multiple projects

Packages in openSUSE should make every effort to avoid having multiple, separate, upstream projects bundled together in a single package.

Specfile guidelines

The rules that apply to content of specfiles are in a separate document called the Specfile guidelines.

Architecture support

All openSUSE packages must successfully compile and build into binary RPM files on at least one of the supported architectures. The packagers should make every effort to make the package build for supported architectures. Content — code which does not need to be compiled or built — and architecture independent code (noarch) are notable exceptions.

Relocatable packages

The use of RPM's facility for generating relocatable packages is strongly discouraged. It is difficult to make it work properly, impossible to use from the installer or from zypper and yast, and not generally necessary if other packaging guidelines are followed. However, in the unlikely event that you have a good reason to make a package relocatable, you MUST state this intent and reasoning in the request for package review.


Banned software

Applications (or other software) listed on the Build Service application blacklist are banned from being packaged.

Donation requests

Packaging content (descriptions/summaries/comments/...) should never contain direct donation requests neither for upstream project nor for the packager. Even if we do not deny need for donations with such projects they should market the campaign on their website and not use downstream packaging for such purposes.

If you find package that is asking for donations at runtime it is up to the packager to keep this code or patch it out.


Software licenses should comply with the Open Source Definition (which is currently at version 1.9). If you are in doubt as to whether a particular license conforms with the Open Source Definition, you should file a bug using the category SUSE Tools -> SUSE Linux Legal Issues.

Spec Files

Packagers usually come into contact with software licensing through the spec file. A spec file can declare the license(s) for the entire package or for individual subpackages. Licenses should be declared using SPDX shortname format. SPDX and its associated list of licenses is relatively young, however, the list is limited and does not cover all licenses an openSUSE packager will typically have to address. To temporarily work around this limitation, we have created a spreadsheet which contains not only the existing SPDX licenses but also many other licenses which are acceptable for either openSUSE Factory or openSUSE NonFree. If you still can't find a suitable license short name there, you should file a bug using the category SUSE Tools -> SUSE Linux Legal Issues.

As well as using a predefined syntax for declaring licenses, openSUSE also recognizes a kind of license grammar in spec files. You can use operators such as 'and' to declare an aggregation of licenses, or 'or' to show that either one or the other license should be chosen. You can also use brackets to gather licenses, for example

License: (MIT or GPL-2.0) and LGPL-2.1+

could be the declaration for a package with an executable binary and a corresponding LGPL-2.1+ licensed library.

Another thing to be aware of when you are writing spec files is that subpackages will inherit the main license of the package if the packager omits to enter a license specifically for that subpackage. This is not always ideal. For example, if you decide to create a separate subpackage for documentation, you should check if the documentation is licensed under e.g. GFDL-1.1 rather than the (e.g.) GPL-2.0+ license of the main package. It is advisable to always add a license to a subpackage - even if the subpackage is coincidentally under the same license as the main package. This makes it immediately obvious to anybody reading the spec file later.

Finally, license texts should always be copied into the package. This is usually done by adding the filename to the %files section using the %license macro in the spec file. Licenses are often found in files with names such as COPYING, COPYING.LIB, LICENSE.txt etc. See for a discussion around that.

Code vs Content

It is important to make distinction between computer executable code and content. While code is permitted (assuming, of course, that it has an open source compatible license, is not legally questionable, etc.), only some kinds of content are permissible. The rule is:

If the content enhances the user experience, then the content is OK to be packaged in openSUSE. This means, for example, that things like: fonts, themes, clipart, and wallpaper are OK.

Content still has to be reviewed for inclusion. It must have an open source compatible license and must not be legally questionable. In addition, there are several additional restrictions for content:

  • Content must not be pornographic, or contain nudity, whether animated, simulated, or photographed. There are better places on the Internet to get porn.
  • Content should not be offensive, discriminatory, or derogatory. If you are not sure if a piece of content is one of these things, it probably is.

Some examples of content which is permissable:

  • Clipart for use in office suites
  • Wallpaper (non-offensive, non-discriminatory, with permission to freely redistribute)
  • Fonts (under an open source license, with no ownership/legal concerns)
  • Game levels are not considered content, since games without levels would be non-functional.
  • Sound or graphics included with the source tarball that the program or theme uses (or the documentation uses) are acceptable.
  • Game music or audio content is permissible, as long as the content is freely distributable without restriction.
  • Example files included with the source tarball are not considered content.

Some examples of content which are not permissible:

  • Comic book art files
  • Religious texts

If you are unsure if something is considered approved content, ask on the openSUSE-packaging mailing list.

Package features

Systemd Services

openSUSE uses systemd to start services. Detailed guidelines how to package service files are at openSUSE:Systemd_packaging_guidelines.

Desktop files

If a package contains a GUI application, then it needs to also include a properly installed .desktop file. For the purposes of these guidelines, a GUI application is defined as any application which draws an X window and runs from within that window. Installed .desktop files MUST follow the desktop-entry-spec, paying particular attention to validating correct usage of the Name, GenericName, Categories and StartupNotify entries.

Icon tag in Desktop Files

The icon tag has to be the basename of the icon file(s), because it allows for icon theming:

  • Icon=comical

It assumes .png by default, then tries .svg and finally .xpm.

.desktop file creation

If the package does not already include and install its own .desktop file, you need to make your own, and include it as a source (e.g. Source3: %name.desktop). The contents of a sample .desktop file (comical.desktop) are:

[Desktop Entry]
GenericName=Comic Archive Reader
Comment=Open .cbr & .cbz files

%suse_update_desktop_file usage

It is not simply enough to just include the .desktop file in the package. One MUST run %suse_update_desktop_file in the %install section, and have BuildRequires: update-desktop-files enlisted, to help ensure .desktop file safety and spec-compliance. %suse_update_desktop_file MUST be used if the package does not install the file or there are changes desired to the .desktop file (such as adding/removing categories, etc). Some examples of usage:

  • check desktop file
%suse_update_desktop_file %{name}
  • install desktop file and change categories
%suse_update_desktop_file -r %{name} System Utility Core GTK FileManager

More information about the macro can be found on the macro page.

Users and Groups

Note that for the moment, we primarily address the case where the mapping from user/group names to uids/gids is decided dynamically by target systems at package install time. Some options for system administrators for making this mapping static even though the package scriptlets use a dynamic scheme are also discussed below, and more are being investigated, including possibilities to make the mapping static at package build time.

System users, which are used by a variety of applications, by standard filesystem directories or are standard users which should exist on every Unix compatible system, should be provided by special RPMs.

This RPMs provides and the user and groups:

Provides: user(<name>)
Provides: group(<name>)

This RPMs are also responsible to create and provide the home directory. Applications needing a special system user should require them:

Requires(pre): user(<name>)
Requires(pre): group(<name>)

With this, the system users will only be created if they are needed. And an admin can easy find out, if a system user is still required or can be deleted.

systemd-sysusers (sysusers.d(5)) is used to create this accounts. This allows to verify how the system account should look like.

An example spec file for the uucp system user should contain the following lines:

Source1:        system-user-uucp.conf
BuildRequires:  sysuser-tools

%package -n system-user-uucp
Summary:        System user and group uucp

%sysusers_generate_pre %{SOURCE1} uucp system-user-uucp.conf

install -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/system-user-uucp.conf

%pre -n system-user-uucp -f uucp.pre

%files -n system-user-uucp

%pre ... -f uucp.pre causes the content of uucp.pre to be taken as scriptlet. This file is generated at build time by the %sysusers_generate_pre-macro.

It is only necessary to add the sysusers config file into a subpackage, if the user should be available to other packages as well. If only the package itself requires the new user, then no subpackage is needed and the spec can be simplified as follows:

Source1:        %{name}-user.conf
BuildRequires:  sysuser-tools

%sysusers_generate_pre %{SOURCE1} %{name} %{name}-user.conf

install -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/%{name}-user.conf

%pre -f %{name}.pre


An example for a %{name}-user.conf file:

#Type Name ID   GECOS                      Home directory Shell
u     uucp -    "Unix-to-Unix CoPy system" /etc/uucp      -
m     uucp lock -                          -              -

More information can be found at the page. On an installed system, you should also find some of these files below /usr/lib/sysusers.d.

Home directory should usually be a directory created and owned by the package, with appropriate restrictive permissions. A good choice for the location of the directory is the package's data directory or directory under /var like /var/lib/NAME, in case it has one.

User accounts created by packages are rarely used for interactive logons, and should thus generally use - for Shell which sets /usr/sbin/nologin as the user's shell. Don't use /bin/false or /bin/true as a shell, change it to /usr/sbin/nologin as described above.

Use the %sysusers_requires macro, do not manually require sysuser-shadow due to changes in the specification from older versions of systemd.

Users or groups created by packages are never removed. There is no sane way to check if files owned by those users/groups are left behind — and even if there were, what would we do to them? Leaving those behind with ownerships pointing to then-nonexistent users/groups may result in security issues when a semantically unrelated user/group is created later and reuses the UID/GID. Also, in some setups, deleting the user/group might not be possible or/nor desirable, for example, when using a shared remote user/group database. Cleanup of unused users/groups is left to the system administrators to take care of, if they so desire.

In some cases it is desirable to create only a group without a user account. Usually, this is because there are some system resources to which we want to control access by using that group, and a separate user account would add no value. Examples of common such cases include (but are not limited to) games whose executables are setgid for the purpose of sharing high score files or the like, and/or software that needs exceptional permissions to some hardware devices and it would not be appropriate to grant those to all system users nor even only those logged in on the console. In these cases, apply only the g line part of the sysusers.d specification.

Note that the practice of not creating users/groups if they already exist has a drawback of possibly unrelated but coincidentally same named existing system users and/or groups unnecessarily and undesirably getting access to things in a package that uses the same user/group names. This version of the users/groups guideline does not address that issue in any way, but it is possible that future revisions will if a good enough way to do that is found.

Migration / Upgrades

With the introduction of containerized deployments it is not a good idea to do all the configuration and generating of random information during the package installation but rather it should be done on the selected spawned container to ensure data consistency and stability.

Basically, it means to move all configuration tasks that are using the binaries and affecting resulting data (e.g. keychain generation, database migration) from scriptlet phases to be executed later on a live system. As our requirement is to be able to install packages using rpm directly we have several options how to achieve this:

systemd services (as a sample see mariadb package)

Use the systemd services to actually do the initial configuration and deployment, alternatively even the migration of the already existing content in place prior start.

General systemd file is quite obvious:

Description=MySQL server 

ExecStartPre=/usr/lib/mysql/mysql-systemd-helper  install
ExecStartPre=/usr/lib/mysql/mysql-systemd-helper  upgrade
ExecStart=/usr/lib/mysql/mysql-systemd-helper     start
ExecStartPost=/usr/lib/mysql/mysql-systemd-helper wait


All the operations are done by the helper shell script that as it is described prior each start does installation and upgrade steps. To ilustrate these points will be different for each application but as a concept this is done in the mariadb:

# Create new empty database if needed
mysql_install() {
	if [ ! -d "$datadir/mysql" ]; then
		echo "Creating MySQL privilege database... "
		mysql_install_db --user="$mysql_daemon_user" --datadir="$datadir" || \
		die "Creation of MySQL databse in $datadir failed"
		echo -n "$MYSQLVER" > "$datadir"/mysql_upgrade_info
# Upgrade database if needed
mysql_upgrade() {
	# Run mysql_upgrade on every package install/upgrade. Not always
	# necessary, but doesn't do any harm.
	if [ -f "$datadir/.run-mysql_upgrade" ]; then
		echo "Checking MySQL configuration for obsolete options..."
		sed -i -e 's|^\([[:blank:]]*\)skip-locking|\1skip-external-locking|' \
		       -e 's|^\([[:blank:]]*skip-federated\)|#\1|' /etc/my.cnf

		# instead of running mysqld --bootstrap, which wouldn't allow
		# us to run mysql_upgrade, we start a full-featured server with
		# --skip-grant-tables and restict access to it by unix
		# permissions of the named socket

		echo "Trying to run upgrade of MySQL databases..."

		# Check whether upgrade process is not already running
		protected="$(cat "/run/mysql/protecteddir.$INSTANCE" 2> /dev/null)"
		if [ -n "$protected" && -d "$protected" ]]; then
			pid="$(cat "$protected/" 2> /dev/null)"
			if [ "$pid" && -d "/proc/$pid" ] &&
			   [ -z "$(readlink "/proc/$pid/exe" | grep -q "mysql")" ]; then
				die "Another upgrade in already in progress!"
				echo "Stale files from previous upgrade detected, cleaned them up"
				rm -rf "$protected"
				rm -f "/run/mysql/protecteddir.$INSTANCE"
		protected="$(mktemp -d -p /var/tmp mysql-protected.XXXXXX | tee "/run/mysql/protecteddir.$INSTANCE")"
		[ -n "$protected" ] || die "Can't create a tmp dir '$protected'"

		# Create a secure tmp dir
		chown --no-dereference "$mysql_daemon_user:$mysql_daemon_group" "$protected" || die "Failed to set group/user to '$protected'"
		chmod 0700  "$protected" || die "Failed to set permissions to '$protected'"

		# Run protected MySQL accessible only though socket in our directory
		echo "Running protected MySQL... "
		/usr/sbin/mysqld \
			--defaults-file="$config" \
			--user="$mysql_daemon_user" \
			--skip-networking \
			--skip-grant-tables \
			$ignore_db_dir \
			--log-error="$protected/log_upgrade_run" \
			--socket="$protected/mysql.sock" \
			--pid-file="$protected/" &

		mysql_wait "$protected/mysql.sock" || die "MySQL didn't start, can't continue"

		# Run upgrade itself
		echo "Running upgrade itself..."
		echo "It will do some chek first and report all errors and tries to correct them"
		if /usr/bin/mysql_upgrade --no-defaults --force --socket="$protected/mysql.sock"; then
			echo "Everything upgraded successfully"
			rm -f "$datadir/.run-mysql_upgrade"
			[ -n "$(grep -q "^$MYSQLVER" "$datadir/mysql_upgrade_info" 2>/dev/null)" ] || \
				echo -n "$MYSQLVER" > "$datadir/mysql_upgrade_info"
			echo "Upgrade failed"

		# Shut down MySQL
		echo "Shuting down protected MySQL"
		kill "$(cat "$protected/")"
		for i in {1..30}; do
			/usr/bin/mysqladmin --socket="$protected/mysql.sock" ping > /dev/null 2>&1 || break
		/usr/bin/mysqladmin --socket="$protected/mysql.sock" ping > /dev/null 2>&1 && kill -9 "$(cat "$protected/")"

		# Cleanup
		echo "Final cleanup"
		if [ -z "$up_ok" ]; then
			rm -rf "$protected" "/run/mysql/protecteddir.$INSTANCE"
			die "Something failed during upgrade, please check logs"


Similar to the previous option but using crontab instead of systemd.

binary tweaks

Adjust binaries to perform all these configuration tasks by themselves on the first run/migration. But it could get complicated in order to persuade upstream projects to accept these changes.

Logrotate scripts

Logrotate requires the log files (or the directory they are in) to be owned as root:root, so it won't be prone to symlink tricks and other attacks.

If the log files have to be owned by a non-root user, the recently added "su" directive should be used. The logrotate process will switch user:group before rotating to match the permissions of the rotated log file.


/var/log/radius/radius.log {
    su radiusd radiusd


See Packaging Patches guidelines.

Supporting multiple versions of a package

See Supporting the installation of multiple package versions


See Creating a (RPM) changes file. Please note that openSUSE uses a separate file for the RPM changelog.

Udev rules

Udev rules files must be placed into %{_udevrulesdir}.

Reloading udev in %post and %postun is not needed, because udev automatically detects changes in rules files (opensuse-packaging discussion).

GConf scriptlets

SuSEfirewall2 Service Definitions

Package types

Some applications have specific guidelines written for them, located on their own pages in the Packaging/ hierarchy.

Architecture crossover (baselib) packages

A.k.a. -32bit, -64bit, -x86, and so on.


How to create and package branding is explained in openSUSE:Packaging_Branding.


Packages should produce useful -debuginfo packages, or explicitly disable them when it is not possible to generate a useful one, but where rpmbuild would do it anyway. Generation of -debuginfo packages happens automatically where possible, and can be explicitly disabled in the Build Service on a per-(project, repository) or per-(package, repository) basis. For example, look at the Debuginfo flags within the OBS web UI's Repository tab for the openSUSE:12.3:Update project or a package within that project. Of course you can also change these flags by using osc to edit the metadata belonging to the project or package. Whenever a -debuginfo package is explicitly disabled, an explanation why this was done so is required in the specfile.

If the debuginfo flag is enabled, bs-worker will trickle this down to build(1) (some env var?) and /usr/bin/build calls rpm with --define '_build_create_debug 1'.

/home/abuild/.rpmmacros is populated with the definition for _build_create_debug, and will cause an expansion of ungodly macro magic, including %debug_package. %debug_package is a standardized RPM component and marks the end of BS-specific logic.

NOTE: Older versions of rpm (still used for SLE-11 builds) don't support noarch sub-packages. This leads to debuginfo files being created but no -debuginfo being generated. When building for those versions, noarch must not be used for sub-packages.

Debuginfo packages are discussed in more detail in a separate document, [1].

Eclipse Plugins


Add single elisp file (.el) in %{_datadir}/emacs/site-lisp. If you add multiple file, make a subdir %{_datadir}/emacs/site-lisp/%{name} and add a new initialization file to add the subdir to the load-path list:

%define _sitedir %{_datadir}/emacs/site-lisp
%define _startfile %{_sitedir}/suse-start-%{name}.el

cat <<EOF > %{buildroot}%{_startfile}
;; %{_startfile}

(add-to-list 'load-path "%{_sitedir}/%{name}")

;; %{_startfile} ends here

TODO: byte-compilation & autoloads


Fonts in general-purpose formats such as Type1, OpenType TT (TTF) or OpenType CFF (OTF) are subject to specific openSUSE:Packaging_Fonts, and should never be packaged in a private application directory instead of the system-wide font repositories.


How to package games is explained in openSUSE:Packaging_Games.



How to package Go software is explained in openSUSE:Packaging_Go.


How to package Haskell software is explained in openSUSE:Packaging_Haskell.


How to package Java software is explained in openSUSE:Packaging_Java


How to package Common Lisp software is explained in openSUSE:Packaging_Lisp


How to package Lua software is explained in openSUSE:Packaging_Lua



OCaml extensions

PAM (Pluggable Authentication Modules)


How to package Perl software is explained in openSUSE:Packaging_Perl


How to package PHP software is explained in openSUSE:Packaging_PHP


How to package Python software is explained in openSUSE:Packaging_Python

PyQt5 and SIP

How to package PyQt5 and software using SIP is explained in openSUSE:Packaging_PyQt5_and_SIP


How to package R software is explained in openSUSE:Packaging_R


How to package Rust software is explained in Packaging Rust Software


How to package Ruby software is explained in openSUSE:Packaging_Ruby


How to package wxWidgets software is explained in openSUSE:Packaging_wxWidgets


Shared Libraries

openSUSE:Shared library packaging policy

GObject Introspection Bindings (.typelibs)

A good amount of libraries, mostly from the GNOME Stack, ship a .typelib file. This .typelib interfaces between various programming languages (seed, python, vala) and the library. Just like shared libraries, they are versioned and can potentially require to be installed in multiple versions.

The package name follows the convention typelib-<GIVersion>-<TypeLibName>-<TypeLibVersion>, where dots (.) are replaced with underscores (_)

Explanation of the <FIELDS>: typelib: Literal, as an identifier of the package type. <GIVersion>: Current gobject introspection version is 1.0, so 1_0 <TypeLibName>: The name of the actual binding, Case sensitive <TypeLibVersion>: The version of the binding

All these elements can be taken out of the filename. For example /usr/lib/girepository-1.0/Memphis-0.2.typelib results in the package name typelib-1_0-Memphis-0_2 When building such packages, make sure that the spec file contains BuildRequires: gobject-introspection, as this triggers the installation of an automatic dependency scanner in plus. As a result, the typelib-* package automatically requires its libraries and possibly other typelib-packages.

Static Libraries

Packages including libraries should exclude static libraries as far as possible (e.g. by configuring with the --disable-static switch). Static libraries should only be included in exceptional circumstances. Applications linking against libraries should, as far as possible, link against shared libraries, not static versions.

Libtool archives, files, should not be included. Packages using libtool will install these by default even if you configure with --disable-static, so they may need to be removed before packaging. Due to bugs in older versions of libtool or bugs in programs that use it, there are times when it is not always possible to remove *.la files without modifying the program. In most cases, it is fairly easy to work with upstream to fix these issues. Note that if you are updating a library in a stable openSUSE release and the package already contains *.la files, removing the *.la files should be treated as an API/ABI change, in other words, removing them changes the interface that the library gives to the rest of the world and should not be undertaken lightly.


We want to be able to track which packages are using static libraries so we can find which packages need to be rebuilt if a security flaw in a static library is fixed, for instance. If you really need to package static libraries, you have to follow this guideline.

Static libraries must be placed in a *-devel-static subpackage, which Requires the *-devel subpackage. Separating the static libraries from the other development files in *-devel allow us to track this usage by checking which packages BuildRequire the *-devel-static package. The intent is that, whenever possible, packages will move away from using these static libraries, to the shared libraries. When a package only provides static libraries, skip the Requires on the *-devel subpackage (this assumes that the *-devel-static subpackage also ships the header files). Packages which explicitly need to link against the static version must then BuildRequire: foo-devel-static, so that the usage can be tracked.

If (and only if) a package has shared libraries which require static libraries to be functional, the static libraries can be included in the *-devel subpackage. The devel subpackage must have a virtual Provide for the *-devel-static package, and packages dependent on it must BuildRequire the *-devel-static package.

Duplication of system libraries

For several reasons, a package should not include or build against a local copy of a library that exists on a system. The package should be patched to use the system libraries. This prevents old bugs and security holes from living on after the core system libraries have been fixed.


Web Applications

Web applications packaged in openSUSE should place their files in /etc, /usr, /var, etc., depending on type, just like any other application would do.

In general, a package must not install, remove or otherwise modify /srv content as its use is reserved to the admin according to FHS[2]. In openSUSE, it's OK to have a package create an empty directory in /srv if the default configuration of the software points there.


How to detect Vala version in specfile

We assume you're not building Vala itself. If so, the version of Vala is defined by youself and can be called simply using %{version} tag, the problem does not even exist. Here we're facing such an embarrassing situation:

The package is built with Vala, and is built well under all versions of Vala(So you don't have to gild the lily by BuildRequires: vala >= 0.14 tag). But you still have to detect and use the version number of Vala.

Here Build Service got a bottleneck. For now, Vala can not simply be included with BuildRequires: vala and exports its version simple and naive with automatic tag like %{py_ver} or %{perl_version} as Perl or Python does. Some package do need to know Vala's number to get its work done(eg.., Babl or Gegl will install .vapi file to /usr/share/vala by default, but that directory is meaningless in a standard openSUSE system. However, openSUSE use directory suffixed with a Vala version number like /usr/share/vala-0.14. Now it is you packager's responsibility to move them from package default directory to openSUSE's callable).

Of course you could, by using %if 0%{?suse_version} >= 1140 tag, move them to the directory suffixed with standard Vala version number, which is installed by default on that 1140 version of openSUSE. But it will make your specfile huge and unmaintainable(you have to add such section for almost every version of openSUSE).

Now we use the self defined function %define in specfile,as follows:

 #import Vala dependency 
 BuildRequires: vala
 #define a function to export Vala's version number
 %define vala_version %(rpm -q --queryformat='%%{VERSION}' vala | sed 's/\.[0-9]*$//g')

We use rpm -q --queryformat='%%{VERSION}' vala to fetch Vala's full version number 0.14.0, which is installed in a standard RPM way before the build process actually starts, that's why such code works. And then use sed shell command with the power of Regex to cut the suffix .0 and export the result to a variable vala_version, which can be called easily by %{vala_version}. eg:

 #create a new directory. -pv means export directories it made into logfile for futher debug 
 #and create it automatically if the parent directory does not exist.
 mkdir -pv %{buildroot}%{_datadir}/vala-%{vala_version}/
 #move the files
 mv %{buildroot}%{_datadir}/vala/* %{buildroot}%{_datadir}/vala-%{vala_version}/
 #delete the original folder
 rm -Rf %{buildroot}%{_datadir}/vala/

Even in %files section, you could use:

 %dir %{_datadir}/vala-%{vala_version}/

to simplify your specfile.

Because every file under %{buildroot} will be packaged by hierarchy into the final output RPM, now your package's user friendliness pops up +1.