Craft/Blueprints

From KDE Community Wiki

Note

This page is work in progress. Please help to make it more useful!


Adding New Blueprints

Blueprints are stored in separate repositories. At the moment there are these repositories:


To navigate to this repository on your local file system:

cs craft-blueprints-kde

If you have an existing blueprint repository and want to add it to your Craft setup, do it like this:

craft --add-blueprint-repository https://github.com/owncloud/craft-blueprints-owncloud.git

If you want to add a new blueprint first of all you have to choose the right location (e.g. kde apps are located in the kde folder.

Warning

The name of the package folder needs to match the blueprint name. An example would be kdegraphics-mobipocket\kdegraphics-mobipocket.py


Structure

The following shows an example blueprint file. Outside of the information in the various fields which define the software package itself, and that there is one file missing (excludelist.txt, which you can see the description of in the comment above the line which references that file), this is what a blueprint looks like. Since there is no project named category/projectname.git on KDE's Invent, the blueprint won't just build, but apart from that, this is what they look like.

import info

class subinfo(info.infoclass):
    # This defines the basic information about your software package (both the basic
    # metainformation like human readable names, where the source comes from, and
    # what other blueprints it depends on).
    def setTargets(self):
        # The human-readable name of the main binary
        self.displayName = "Your Project"
        # A description of the entire package
        self.description = "A really solid software package that does nifty things"
        # The project's webpage (if you're unsure for a KDE project, just use this one)
        self.webpage = "https://kde.org"
        # You can set various targets. By convention, call your primary one "master"
        # and then give the git URL for the project. If you need to specify a branch,
        # you can do so by adding a pipe and the name of the branch (or indeed tag)
        # to the line
        self.svnTargets["master"] = "https://invent.kde.org/category/projectname.git|branch"
        # You can have multiple targets, each with a unique name.
        # To select a specific commit (or tag) add TWO pipes and the commit hash or tag name
        # to the line
        self.svnTargets["somecommit"] = "https://invent.kde.org/category/projectname.git||commithash"
        # The default target is what Craft will use to build your package if it is not
        # told anything else (by either the command line, or another blueprint which
        # depends on yours).
        self.defaultTarget = "master"
    def setDependencies( self ):
        # Defines the blueprints this blueprint depends on, and which target (default is what
        # was defined above, and is usually what you would write, except in highly specific
        # cases). These are the directories inside the blueprints directory which contain
        # the blueprint for the thing this blueprint depends on.
        # A buildDependencies entry is something required for actually building the
        # software in this blueprint
        self.buildDependencies["dev-utils/pkg-config"] = None
        # a runtimeDependencies entry is something which must also be installed for the
        # software to function (and which will also then be included in any package you
        # build using Craft)
        self.runtimeDependencies["libs/qt5/qtbase"] = None
        # If there are compiler specific things to consider, either a library you need
        # for a specific compiler and not for others, you can use CraftCore.compiler to
        # make such checks.
        if CraftCore.compiler.isMinGW():
            self.runtimeDependencies["libs/runtime"] = None #mingw-based builds need this

from Package.CMakePackageBase import CMakePackageBase # The package base

class Package(CMakePackageBase):
    # This defines which build system your blueprint should use. In this case, we are
    # using the CMake package base, but there are a lot of options for specific use cases,
    # such as Meson, Perl, QMake, and Binary ones. See 
    # https://invent.kde.org/packaging/craft/-/tree/master/bin/Package
    # for details on which package base options are available. For KDE projects, however
    # you will almost certainly be using CMake, and the others are commonly more useful
    # for when you are creating blueprints for new dependencies.
    def __init__( self ):
        # Always remember to just initialize the package base like so
        super().__init__()
        # If you have tests set up to build by default, for example, you might want to
        # disable those for Craft builds (usually, in KDE, those tests are more useful
        # for the CI system, and less useful for Craft, which is more useful for creating
        # installer packages and the like, not for automated testing purposes). You can
        # do this by setting the following option.
        CMakePackageBase.buildTests = False

    def createPackage(self):
        # Usually you will not need this entry, but in case your main executable is
        # called something else than the blueprint's name, you can set that here.
        # This allows Craft to pass this information to tools which build packages,
        # such as the one which builds appimages, which will then be able to work
        # on the correct executable.
        self.defines["appname"] = "mainexecutable"
        # For Windows, similarly to above, if your application is called something
        # other than your blueprint's name, you can explicitly pass in an icon from
        # somewhere on the Craft filesystem. Here we pick out an ico file from inside
        # the 
        self.defines["icon"] = os.path.join(self.sourceDir(), "gemini", "calligragemini.ico")
        # For Windows, you can define a set of shortcuts by setting the shortcuts define with
        # multiple values, such as below:
        self.defines["shortcuts"] = [{"name" : self.subinfo.displayName, "target":"bin/mainexecutable.exe"},
                                     {"name" : "The Other Application", "target" : "bin/differentexecutable.exe"},
                                     {"name" : "A Bonus Tool", "target" : "bin/anotherexecutable.exe"}]
        # If you have files that get installed automatically, but which you know are
        # in fact not needed for the application to run (this will sometimes be the
        # case for example for building Windows packages, where you don't need some
        # of the things installed by some dependencies), you can list those files
        # in a list in some file, which is a list of regular expressions which will
        # be interpreted on a per-line basis, and any file which is matched by any line
        # will not be included in the package.
        self.blacklist_file.append(os.path.join(self.packageDir(), "excludelist.txt"))
        # Alternatively, you can add a direct filter on specific files by adding
        # lines like this one (which will cause Craft to not package any executable
        # file that is outside the two directories at the start, and is not named
        # one of the four names in the second paranthesis).
        self.addExecutableFilter(r"(bin|libexec)/(?!(mainexecutable|differentexecutable|anotherexecutable|update-mime-database)).*")

        # You can add packages that should be ignored for packaging purposes. This is
        # in many ways similar to adding a buildDependencies entry, but only ignores
        # this specific package (which can be handy if other things pull in a package
        # that your software doesn't need when publishing).
        self.ignoredPackages.append("dev-utils/sed")

        # In some cases, you need to do things depending on specific conditions,
        # such as building on anything that is not Linux, where you might wish to
        # not ship dbus. You can do this like so:
        if not CraftCore.compiler.isLinux:
            self.ignoredPackages.append("libs/dbus")

        # Finally, just call the packager itself to get the package actually created.
        return super().createPackage()

Dependencies

The various things you can list as dependencies (and indeed reference in places which reference other blueprints) are the directory names in which you can find the python files which define other blueprints in this repository: https://invent.kde.org/packaging/craft-blueprints-kde/

Package Base

See https://invent.kde.org/packaging/craft/-/tree/master/bin/Package for available package bases

Howto

Apply Patches

Apply a simple patch

# ...
    def setTargets(self):
        # ...
        # "5.81.0" is the version the patch should be applied
        self.patchToApply["5.81.0"] = [("patch-file.diff", 1)]  # patch file, patch depth (= git apply -p<n> option)
        self.patchLevel["5.81.0"] = 1

It is also possible to do fancy stuff like

# ...
    def setTargets(self):
        # ...
        for ver in ["master"] + self.versionInfo.tarballs():
            self.patchToApply[ver] = [("patch-file.diff", 1)]
            self.patchLevel[ver] = 1

Check for Compiler/OS

If you want to run a command based on the current environment you can use CraftCore.compiler

if CraftCore.compiler.isWindows:
    # do something only on windows
if not CraftCore.compiler.isGCC:
    # don't do something with GCC

Take a look at https://invent.kde.org/packaging/craft/-/blob/master/bin/CraftCompiler.py to see all available options