diff --git a/CMake/generate.conf b/CMake/generate.conf
index 677c346e930a77ef7db3315436c2534d4528c5d2..a45d50ea6919ad68f4b95e0a123548f6c71d437e 100644
--- a/CMake/generate.conf
+++ b/CMake/generate.conf
@@ -8,6 +8,10 @@ patch = 1
 ProjectConfFile: project.conf
 PathToSources: ..
 
+[Groups]
+cli
+gui
+
 [CMake:Categories]
 ; projects and their templates
 ; project must belong in one of these categories
diff --git a/CMake/generate.py b/CMake/generate.py
index 8da61bfb0215ace04645997eed1b5192a9977c7b..2d7698a467e743d8d1fecd7ec74acbb491722b27 100755
--- a/CMake/generate.py
+++ b/CMake/generate.py
@@ -4,6 +4,7 @@ import os
 import sys
 import configparser
 import argparse
+import itertools
 
 
 # ----------------------------------------------------------------------------------------------------------------------
@@ -98,6 +99,10 @@ class Project:
         self._config = configparser.ConfigParser(allow_no_value=True)
         self._config.read(Helpers.path(self.path, conf))
 
+    @property
+    def groups(self):
+        return Config.get_array(self._config, 'General', 'groups', fallback=[])
+
     @property
     def category(self):
         ctg = Config.get(self._config, 'General', 'category')
@@ -177,6 +182,8 @@ class Project:
                     stack.append(dep)
         return visited
 
+    def __repr__(self):
+        return '<Project: {}>'.format(self.name)
 
 # ----------------------------------------------------------------------------------------------------------------------
 
@@ -270,6 +277,9 @@ PROJECTS = {
     project: Project(project, Config.get(CONFIG, 'General', 'ProjectConfFile'))
     for project in Helpers.find_dirs_with_file(ALIB_DIRECTORY, Config.get(CONFIG, 'General', 'ProjectConfFile'))
 }
+GROUPS = {
+    group: [p for n, p in PROJECTS.items() if group in p.groups] for group in Config.get_section(CONFIG, 'Groups').keys()
+}
 CATEGORIES = {
     category: SmartOpen(template, directory=SCRIPT_DIRECTORY, mode='r').read()
     for category, template in Config.get_section(CONFIG, 'CMake:Categories').items()
@@ -278,29 +288,41 @@ CATEGORIES = {
 
 # ----------------------------------------------------------------------------------------------------------------------
 
+def compute_packages(packages, groups, no_dependencies, verbose):
+    # filter out invalid
+    for g in groups:
+        if g not in GROUPS:
+            print("Warning: Package group {} is not a valid group. Skipping".format(g), file=sys.stderr)
+
+    for p in packages:
+        if p not in PROJECTS:
+            print("Warning: Package {} is not a valid project. Skipping".format(p), file=sys.stderr)
+
+    packages = [p for p in packages if p in PROJECTS]
+    groups = [g for g in groups if g in GROUPS]
+    packages = [PROJECTS[p] for p in packages] + list(itertools.chain.from_iterable([v for k, v in GROUPS.items() if k in groups]))
+
+    res = set()
+
+    # no packages specified, i.e. return all
+    if len(packages) is 0:
+        return PROJECTS.values()
 
-def main(dry_run, main_file, packages_requested, no_dependencies, only_show_dependencies):
-    if packages_requested is None:
-        packages = PROJECTS.values()
-    else:
-        # Filter out invalid packages and possibly compute dependencies
-        packages = set()
-        for p in packages_requested:
-            if p not in PROJECTS:
-                print("Warning: Package {} is not a valid project. Skipping".format(p), file=sys.stderr)
-                continue
+    # possibly compute dependencies
+    for p in packages:
+        if verbose:
+            p_dependencies_str = map(lambda s: "'{}'".format(s), PROJECTS[p].dependencies)
+            print("Project '{}' depends on: {}".format(p, Helpers.join(p_dependencies_str, ', ')), file=sys.stderr)
 
-            if only_show_dependencies:
-                pdeps = map(lambda s: "'{}'".format(s), PROJECTS[p].dependencies)
-                print("Project '{}' depends on: {}".format(p, Helpers.join(pdeps, ', ')), file=sys.stderr)
+        if no_dependencies:
+            res.add(p)
+        else:
+            res |= {PROJECTS[d] for d in p.recursive_dependencies()}
+    return res
 
-            if no_dependencies:
-                packages.add(p)
-            else:
-                for dep in PROJECTS[p].recursive_dependencies():
-                    packages.add(dep)
 
-        packages = [PROJECTS[p] for p in set(packages)]
+def main(dry_run, main_file, packages_requested, groups_requested, no_dependencies, only_show_dependencies):
+    packages = compute_packages(packages_requested, groups_requested, no_dependencies, only_show_dependencies)
 
     print("The following packages will be generated:", file=sys.stderr)
     print(", ".join(sorted([p.name for p in packages])), file=sys.stderr)
@@ -333,9 +355,10 @@ if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='CMakeLists generator for Algorithms Library Toolkit')
     parser.add_argument('-w', '--write', action='store_true', default=False, help="Write files")
     parser.add_argument('-m', '--main', action='store_true', default=False, help="Generate main CMakeLists.txt")
-    parser.add_argument('-p', '--package', action='append', help='Specify packages to build. For multiple packages, use -p multiple times')
+    parser.add_argument('-p', '--package', action='append', help='Specify packages to build. For multiple packages, use -p multiple times. Can be combined with -g')
+    parser.add_argument('-g', '--group', action='append', help='Specify package group to build. For multiple groups, use -g multiple times. Can be combined with -p')
     parser.add_argument('--no-deps', action='store_true', default=False, help="Don't generate dependencies")
-    parser.add_argument('--show-deps', action='store_true', default=False, help='Only show dependencies of selected packages')
+    parser.add_argument('--show-deps', action='store_true', default=False, help='Only show dependencies of selected packages or groups')
     parser.add_argument('-v', '--version', action='store_true', default=False, help="Show current version")
 
     args = parser.parse_args()
@@ -346,4 +369,9 @@ if __name__ == '__main__':
 	        Config.get(CONFIG, 'Versioning', 'patch')))
         sys.exit(0)
 
-    sys.exit(main(not args.write, args.main, args.package, args.no_deps, args.show_deps))
+    sys.exit(main(not args.write,
+                  args.main,
+                  args.package if args.package else [],
+                  args.group if args.group else [],
+                  args.no_deps,
+                  args.show_deps))
diff --git a/agui2/project.conf b/agui2/project.conf
index 9b9617c589ebe4e04a4d7b9211f8ca4991c8d840..dbaa4230b645aa431314e5ef5bf65ccdc7d7559e 100644
--- a/agui2/project.conf
+++ b/agui2/project.conf
@@ -1,5 +1,6 @@
 [General]
 category: executable
+groups: gui
 
 [Dependencies]
 project: alib2algo alib2aux alib2data alib2str alib2gui
diff --git a/alib2gui/project.conf b/alib2gui/project.conf
index 54ccc707e336e99126a9ae149e54fc52c663b03f..dfd77298f9f04d0e7af2f4b55067c452cb5bbd46 100644
--- a/alib2gui/project.conf
+++ b/alib2gui/project.conf
@@ -1,5 +1,6 @@
 [General]
 category: library
+groups: gui
 
 [Dependencies]
 project: alib2str alib2xml alib2common alib2abstraction alib2measure alib2std
diff --git a/aql2/project.conf b/aql2/project.conf
index fe8b512e0a65792b91bf31b4ef071844d2c63c93..2207bc5e0d6f4e187bfe490846e5ada8f1224195 100644
--- a/aql2/project.conf
+++ b/aql2/project.conf
@@ -1,5 +1,6 @@
 [General]
 category: executable
+groups: cli
 
 [Dependencies]
 project: alib2elgo alib2graph_algo alib2algo alib2aux alib2raw_cli_integration alib2raw alib2str_cli_integration alib2str alib2graph_data alib2data alib2dummy alib2cli alib2xml alib2common alib2abstraction alib2measure alib2std