We know that Snapcraft’s plugin framework is extensible. We can define a new Custom plugin for our Snapcraft to build our project based on the new requirements. In today’s article, we will show you how to create a new plugin for our Snapcraft. For more information about snapcraft’s plugin, please refer to our documentation. For more information about Snapcraft, see our website snapcraft. IO. We can also find a number of plugin routines in the address for our reference.
\
1) Create a basic Custom Plugin
\
\
At present, the closest documents we can find on the web are:
\
Snapcraft. IO/docs/build -… \
\
As required, we create a template for the most basic Custom Plugin. We can download our code as follows:
\
$ git clone https://github.com/liu-xiao-guo/plugin_template
Copy the code
The document structure of our project:
\
Liuxg @ liuxg: ~ / release/plugin_template $tree - L 3. ├ ─ ─ parts │ └ ─ ─ the plugins │ └ ─ ─ myplugin. Py └ ─ ─ snapcraft. YamlCopy the code
\
In the file schema above, we can see that we created a directory called parts under the root of our project. Underneath it, we also created a subdirectory called plugins. Inside we created a basic file, myplugin.py. It reads as follows:
\
myplugin.py
\
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- import logging import os import glob from pprint import pprint import snapcraft logger = logging.getLogger(__name__) def _dump(name, obj): for attr in dir(obj): # logger.warning("obj.%s = %s", attr, getattr(obj, attr)) func = getattr(obj, attr, None) if func: logger.warning("%s.%s = %s",name, attr, func) logger.warning("===================================") class MyPlugin(snapcraft.BasePlugin): @classmethod def schema(cls): schema = super().schema() schema['properties']['myprop'] = { 'type': 'string', 'default': '' } # Inform Snapcraft of the properties associated with building. If these # change in the YAML Snapcraft will consider the build step dirty. schema['build-properties'].append('myprop') return schema def __init__(self, name, options, project): super().__init__(name, options, project) logger.warning("__init__ begins name: %s", name) logger.warning("options.source: %s", options.source) logger.warning("options.myprop: %s", options.myprop) logger.warning("self.builddir: %s", self.builddir) logger.warning("self.installdir: %s", self.installdir) # logger.warning("self.project.parallel_build_count: %d", self.project.parallel_build_count) # logger.warning("self.project.arch_triplet: %s", self.project.arch_triplet) # logger.warning("self.project.stage_dir: %s", self.project.stage_dir) logger.warning("Going to add the needed build packages..." ) # self.build_packages.append('golang-go') # _dump("options", options) # _dump("project", project) logger.warning("build-packages:") for pkg in options.build_packages: logger.warning("build package: %s", pkg) def build(self): # setup build directory super().build() logger.warning("build begins ... ") def pull(self): super().pull() logger.warning("pull begins ... ") def clean_pull(self): super().clean_pull() logger.warning("clean pull begins ... ")Copy the code
\
Here, we can see a basic and simple plugin template. Our MyPlugin inherits from Snapcraft.BasePlugin. There’s nothing really going on in there. It’s worth pointing out here that we used logger.warning(…) To help us output debugging information. On the actual release, we need to delete the output. We know that when Snapcraft builds a project, it goes through the following process:
\
\
\
\
A plugin undergoes pull and build in the above five processes. We can also look at many examples of plugins in the Snapcraft project to see how to implement the method above.
In the code above:
\
schema['properties']['myprop'] = {
'type': 'string',
'default': ''
}
Copy the code
\
We also define our own property called MyProp. To show our invocation of the custom snapcraft plugin, we designed the following snapcraft. Yaml file:
\
snapcraft.yaml
name: plugin # you probably want to 'snapcraft register <name>1. 'version: '1' # task This is a sample plugin for snapcraft # 79 char long summary description: | This is a plugin example grade: stable # must be 'stable' to release into candidate/stable channels confinement: strict # use 'strict' once you have the right plugs and slots parts: my-part: # This is a custom plugin defined in "parts" dir plugin: myplugin # This is the property defined in the plugin. It is a file name # to be used for the congfigure hook myprop: configure source: .Copy the code
Here we use myPlugin, and we also use the MyProp property defined in our plugin as configure. source, which is usually an essential property of a plugin. In our routine, we define it as “.” , which is the current directory.
\
Now that our basic plugin framework is in place, let’s build our project. Type in the root directory of the project:
\
$ snapcraft
Copy the code
\
\
\
From the above we can see that it has successfully built our project using our own plugin, and we can see that “pull begins… Build begins… With the words “. These are logger.warning(…) in myplugin.py above us. The output. We also print out some Object properties that we may need, such as self.builddir, etc. We can also see that our options.myprop has a value of “configure”. Similarly, we can clean our project:
\
$ snapcraft clean
Copy the code
\
\
As you can see above, “clean_pull(self)” defined in myplugin.py is also called. We can use this method to delete anything we downloaded in pull.
\
So far, our basic template plugin doesn’t do anything real, it just shows calls to the most basic methods (such as build(self)) inherited from the Snapcraft.BasePlugin class. In the following sections, we’ll show how to use these methods to do what we need to do.
\
\
2) Create a simple app that uses the Snapcraft plugin
\
\
In this section, we’ll show you how to use our plugin above to do something meaningful. In today’s routine, we want to type the following configure file into our project’s final SNAP package:
\
Liuxg @ liuxg: ~ / after/desktop/plugins/prime $tree - L 3. ├ ─ ─ bin │ └ ─ ─ the config ├ ─ ─ the command - config. Wrapper └ ─ ─ meta ├ ─ ─ ├ ─ sci-imp ─ sci-impCopy the code
\
Once we produce this configure file in the Meta /hooks directory, it looks like this:
\
configure
\
#! /bin/sh if ! username=$(snapctl get username); then echo "Username is required" exit 1 fi if ! password=$(snapctl get password); then echo "Password is required" exit 1 fi # Handle username and password, perhaps write to a credential file of some sort. echo "user=$username" > $SNAP_DATA/credentials echo "password=$password" >> $SNAP_DATA/credentials chmod 600 $SNAP_DATA/credentialsCopy the code
Once we have the above configure file in our Snap installation, we can use the following command on our Ubuntu Core system:
\
$ sudo snap set plugin username=foo password=bar
Copy the code
To produce the credentials file in the $SNAP_DATA directory. It reads as follows:
\
\
\
For some reason, this is currently only available on Ubuntu Core (raspberry PI, Dragon Board, KVM, etc.). There are still a few issues in the Ubuntu Desktop environment.
With this in mind, we rewrote our myplugin.py file:
\
myplugin.py
\
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- import logging import os import glob from pprint import pprint import snapcraft logger = logging.getLogger(__name__) def _dump(name, obj): for attr in dir(obj): # logger.warning("obj.%s = %s", attr, getattr(obj, attr)) func = getattr(obj, attr, None) if func: logger.warning("%s.%s = %s",name, attr, func) logger.warning("===================================") class MyPlugin(snapcraft.BasePlugin): @classmethod def schema(cls): schema = super().schema() schema['properties']['myprop'] = { 'type': 'string', 'default': '' } # Inform Snapcraft of the properties associated with building. If these # change in the YAML Snapcraft will consider the build step dirty. schema['build-properties'].append('myprop') return schema def _copy(self, path_copyto): path = os.path.join(path_copyto, "meta/hooks/") logger.warning("path: %s", path); os.makedirs(os.path.dirname(path), exist_ok=True) source = self.builddir + "/" + self.options.myprop logger.warning("source: %s", source) destination = path + self.options.myprop logger.warning("destination: %s", destination) snapcraft.common.link_or_copy(source, destination, False) def __init__(self, name, options, project): super().__init__(name, options, project) logger.warning("__init__ begins name: %s", name) logger.warning("options.source: %s", options.source) logger.warning("options.myprop: %s", options.myprop) logger.warning("self.builddir: %s", self.builddir) logger.warning("self.installdir: %s", self.installdir) # logger.warning("self.project.parallel_build_count: %d", self.project.parallel_build_count) # logger.warning("self.project.arch_triplet: %s", self.project.arch_triplet) # logger.warning("self.project.stage_dir: %s", self.project.stage_dir) logger.warning("Going to add the needed build packages..." ) # self.build_packages.append('golang-go') #_dump("options", options) #_dump("project", project) logger.warning("build-packages:") for pkg in options.build_packages: logger.warning("build package: %s", pkg) def build(self): # setup build directory super().build() logger.warning("build begins ... ") # copy congfigure file to parts/<part>/build/meta/hooks
self._copy(self.builddir)
# copy configure file to parts/<part>/install/meta/hooks
self._copy(self.installdir)
def pull(self):
super().pull()
logger.warning("pull begins ... ")
def clean_pull(self):
super().clean_pull()
logger.warning("clean pull begins ... ")
Copy the code
\
Here, we check our configure files into the build and install directories of our respective parts. And then our job is done. The main change we made was in “Build (self)”. Similarly, we have made the following changes to our snapcraft. Yaml:
\
snapcraft.yaml
\
name: plugin # you probably want to 'snapcraft register <name>1. 'version: '1' # task This is a sample plugin for snapcraft # 79 char long summary description: | This is a plugin example grade: stable # must be 'stable' to release into candidate/stable channels confinement: strict # use 'strict' once you have the right plugs and slots apps: config: command: bin/config parts: my-part: # This is a custom plugin defined in "parts" dir plugin: myplugin # This is the property defined in the plugin. It is a file name # to be used for the congfigure hook myprop: configure source: . config: plugin: dump source: ./bin organize: "*": bin/Copy the code
Here, we added an app called Config. It can be used to show the results of our setup:
\
config
\
#! /bin/bash echo "Reading config file at: $SNAP_DATA" config_file="$SNAP_DATA/credentials" if [ -f "$config_file" ] then cat $config_file else echo "$config_file not found." echo "Please run the following commmand to generate the file:" echo "sudo snap set plugin username=foo password=bar" fiCopy the code
Rebuild our project:
\
\
\
You can see from the above that we have some debugging information output in the middle. Of course we can do whatever we need to do, such as installing the respective Debian packages according to the build-packages definition.
\
We can also test our application:
\
\
In fact, we can also do this directly using the dump plugin provided by our Snapcraft. Interested developers can refer to my article “How to set up our Ubuntu Core application”. From this example we show how to create a custom plugin for our own Snapcraft.
All source code at: github.com/liu-xiao-gu…
\
More on how to design your own Custom Plugin:
– Snappy Ubuntu Core Clinic (YouTube)
-Snappy Ubuntu Core Clinic (Youku) \
\