diff --git a/README.md b/README.md
index 82bcb5bc38cd66792db9c63e8f548bb70df74cc4..158955d74c0571e8ceadb36006e1c7cf4d74f97e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 <!----------------------------------------------------------------------------->
-# gitlab-ci
+# gitlab-ci Repository
 <!----------------------------------------------------------------------------->
 
 This repository contains **GitLab CI scripts** for Yocto infrastructure tasks,
@@ -53,3 +53,14 @@ repository to all repositories that are using it.
 â–¶ [gitlab-ci Deployment][7]
 
 [7]: docs/gitlab-ci-deployment.md
+
+<!----------------------------------------------------------------------------->
+# Manifest pipeline
+<!----------------------------------------------------------------------------->
+
+See this chapter for the pipeline runnning in the manifest repository to build
+the images.
+
+â–¶ [manifest Pipeline][8]
+
+[8]: docs/manifest-pipeline.md
diff --git a/build-jobs.jinja2 b/build-jobs.jinja2
new file mode 100644
index 0000000000000000000000000000000000000000..3aa26b4b68ebcc78a691ac37b3494a48deba9afb
--- /dev/null
+++ b/build-jobs.jinja2
@@ -0,0 +1,162 @@
+---
+#======================================================
+# Create build, test and deploy jobs for all machines
+#======================================================
+
+# As the trigger job is not executed in a environment
+# with checked out repository, we need to get the includes
+# directly from gitlab
+include:
+  - project: '{{ ci_project_root_namespace }}/yocto/infrastructure/gitlab-ci'
+    ref: {{ gitlab_ci_current_rev }}
+    file: 'common.yml'
+  - project: '{{ ci_project_root_namespace }}/yocto/infrastructure/gitlab-ci'
+    ref: {{ gitlab_ci_current_rev }}
+    file: 'manifest-build.yml'
+  - project: '{{ ci_project_root_namespace }}/yocto/infrastructure/gitlab-ci'
+    ref: {{ gitlab_ci_current_rev }}
+    file: 'manifest-package.yml'
+
+workflow:
+  rules:
+    # This rule is needed, as otherwise the workflow:rules from
+    # the parent job seem to be used and prevent the pipeline generation
+    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
+
+stages:
+  - infrastructure
+  - build
+  - test
+  - deploy
+  - uploadftp
+
+# --------------------------------------------------------------------------------------
+# Stage: infrastructure
+# --------------------------------------------------------------------------------------
+
+changelog:
+  extends: .infrastructure
+  script: .gitlab-ci/scripts/changelog_generator.py
+              --token=${GITBOT_TOKEN}
+              --branch {{ master_branch_manifest }}
+              > changelog.md
+  artifacts:
+    expire_in: 4 weeks
+    paths:
+      - "changelog.md"
+
+# --------------------------------------------------------------------------------------
+# Generated build jobs
+# --------------------------------------------------------------------------------------
+
+{% if distro is not defined %}
+{% set distro = "guf-wayland" %}
+{% endif %}
+{% if distro_fng is not defined %}
+{% set distro_fng = "guf-fngsystem" %}
+{% endif %}
+
+{% if optional_arguments is defined %}
+
+{% for machine in optional_arguments %}
+
+{% if yocto_image %}
+
+# Build jobs for the normal yocto image
+build-{{ machine }}:
+  extends: .buildimage
+  stage: build
+  variables:
+    CI_PARAM_MACHINE: {{ machine }}
+    CI_PARAM_DISTRO: {{ distro }}
+    CI_PARAM_IMAGE: {{ yocto_image }}
+
+# Build jobs for the sdk
+buildsdk-{{ machine }}:
+  extends: .buildsdk
+  stage: build
+  variables:
+    CI_PARAM_MACHINE: {{ machine }}
+    CI_PARAM_DISTRO: {{ distro }}
+    CI_PARAM_IMAGE: {{ yocto_image }}
+
+# Deploy jobs for the yocto image
+deployimage-{{ machine }}:
+  extends: .deployimage
+  stage: deploy
+  needs: [build-{{ machine }}, changelog]
+
+# Upload ftp jobs for the yocto image
+uploadftp-{{ machine }}:
+  extends:
+    - .uploadftp
+    - .uploadsdkftp
+  stage: uploadftp
+  needs: [build-{{ machine }}, buildsdk-{{ machine }}, changelog]
+
+# Run platform tests for this machine which the yocto image
+# This is a little hacky as we need to match the machine name to
+# the available platforms
+{% if machine == 'imx6guf' %}
+{% set platforms = "santaro santoka santino santino-lt" %}
+{% elif machine == 'imx6ullguf' %}
+{% set platforms = "nallino" %}
+{% elif machine == 'imx8mguf' %}
+{% set platforms = "tanaro" %}
+{% else %}
+{% set platforms = '' %}
+{% endif %}
+
+{% if platforms %}
+# Run smoketests for this machine which the yocto image
+smoketest:{{ machine }}:
+  extends: .test
+  stage: test
+  needs:
+    - job: build-{{ machine }}
+  variables:
+    CI_PARAM_MACHINE: {{ machine }}
+    CI_PARAM_PLATFORMS: {{ machine }}
+    CI_PARAM_TEST_SUITE: boot.jinja2
+    CI_PARAM_EXTRA: --all-devices
+
+platformtest:{{ machine }}:
+  extends: .test
+  stage: test
+  needs:
+    - job: build-{{ machine }}
+  variables:
+    CI_PARAM_MACHINE: {{ machine }}
+    CI_PARAM_PLATFORMS: {{ platforms }}
+{% endif %}
+
+{% endif %} # if yocto_image is defined
+
+{% if fngsystem_image %}
+
+# Build jobs for the fng system image
+build-{{ machine }}-fngsystem:
+  extends: .buildfng
+  stage: build
+  variables:
+    CI_PARAM_MACHINE: {{ machine }}
+    CI_PARAM_DISTRO: {{ distro_fng }}
+    CI_PARAM_IMAGE: {{ fngsystem_image }}
+
+# Deploy jobs for the fngsystem image
+deployimage-{{ machine }}-fngsystem:
+  extends: .deployimage
+  stage: deploy
+  needs: [build-{{ machine }}-fngsystem, changelog]
+
+# Upload ftp jobs for the fngsystem image
+uploadftp-{{ machine }}-fngsystem:
+  extends: .uploadftp
+  stage: uploadftp
+  needs: [build-{{ machine }}-fngsystem, changelog]
+
+{% endif %} # if fngsystem_image is defined
+
+{% endfor %}
+{% endif %}
+
diff --git a/common.yml b/common.yml
index ea0ccf608c890b3aac22ca028769da89e914406b..186a45336d0ad491c27fafc64d38b8803ac53877 100644
--- a/common.yml
+++ b/common.yml
@@ -45,6 +45,10 @@ variables:
 yamllint:
   extends: .infrastructure
   rules:
+    # Only run this job in the 'parent' pipeline in the manifest, not again in i
+    # the child pipeline.
+    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
+      when: never
     - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
   script:
     - yamllint -c .gitlab-ci/.yamllint.yml .*.yml
diff --git a/docs/manifest-parent-child.png b/docs/manifest-parent-child.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c3001def9c0e735a0b46091febe213631cbd4fa
Binary files /dev/null and b/docs/manifest-parent-child.png differ
diff --git a/docs/manifest-pipeline.md b/docs/manifest-pipeline.md
new file mode 100644
index 0000000000000000000000000000000000000000..d4f8ec86db296b5b664d3fe76b587f49394df57b
--- /dev/null
+++ b/docs/manifest-pipeline.md
@@ -0,0 +1,63 @@
+<!----------------------------------------------------------------------------->
+# Manifest pipeline
+<!----------------------------------------------------------------------------->
+
+The pipeline running in the manifest repository is the one that does the real
+work, like building images, generating docs and deploying image to download
+servers and triggering tests in the lava test rack.
+
+<!----------------------------------------------------------------------------->
+## Triggers
+<!----------------------------------------------------------------------------->
+
+TODO
+
+<!----------------------------------------------------------------------------->
+## Build jobs
+<!----------------------------------------------------------------------------->
+
+TODO
+
+<!----------------------------------------------------------------------------->
+## Deploy and upload jobs
+<!----------------------------------------------------------------------------->
+
+TODO
+
+<!----------------------------------------------------------------------------->
+## Test jobs
+<!----------------------------------------------------------------------------->
+
+TODO
+
+<!----------------------------------------------------------------------------->
+## Job generation, parent child jobs
+<!----------------------------------------------------------------------------->
+
+As we support images for multiple machines/architectures we are building a lot
+of different image variants, where each needs at least build, deploy and test
+job.
+To simplify the generation of all these jobs, without a lot of copy and paste
+and the possibility to configure the actual build images, we are using gitlab's
+dynamic-child-pipeline feature. [See gitlab docs.][1]
+
+[1]: https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html#dynamic-child-pipeline-example
+
+There is a *'generate-build-jobs'* job, that creates a yaml file containing the
+pipeline with all needed jobs.
+There are the following CI variables controlling the content:
+
+* `CI_PARAM_MACHINES`: Space separated list of machines to build for, like "santaro santoka" 
+* `CI_PARAM_IMAGE`: The name of the image to build. If set to an empty string, 
+    related jobs are not created.
+* `CI_PARAM_DISTRO`: The name of the distro to build
+
+* `CI_PARAM_IMAGE_FNG`: The name of the fngsystem image to build. If set to an
+    empty string, related jobs are not created.
+* `CI_PARAM_DISTRO_FNG`: The name of the fngsystem distro to build
+
+It uses a python script called `generate_job_from_template.py` to convert the
+`build-jobs.jinja2` to `build-jobs.yml`. This yml file is then used by the
+*'trigger-build-jobs'* job, to setup the pipeline described by it.
+
+![Manifest's parent child pipeline](manifest-parent-child.png)
diff --git a/gitlab-ci-integration.jinja2 b/gitlab-ci-integration.jinja2
deleted file mode 100644
index 8fc3d663de5bb18d34da9d652da2073732c791bd..0000000000000000000000000000000000000000
--- a/gitlab-ci-integration.jinja2
+++ /dev/null
@@ -1,56 +0,0 @@
----
-#======================================================
-# Trigger the integration pipeline for all repos using 
-# the gitlab-ci repository.
-#======================================================
-
-{% if image is defined %}
-image: {{ image }}
-{% endif %}
-
-workflow:
-  rules:
-    # This rule is needed, as otherwise the workflow:rules from
-    # the parent job seem to be used and prevent the pipeline generation
-    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
-
-stages:
-  - integrate
-  - build
-
-default:
-  tags:
-    # All jobs should use the infrastructure runner
-    - infrastructure
-
-{% set projectneeds = { 'project': "" } %}
-{% if optional_arguments is defined %}
-{% set prev_project = "" %}
-{% for project in optional_arguments %}
-{% set projectshort = project.split('/')[-1] %}
-{{ projectshort }}:
-  stage: integrate
-  needs: [ {{ projectneeds.project }} ]
-{% if parent_merge_request is defined %}
-  variables:
-    parent_merge_request: {{ parent_merge_request }}
-{% endif %}
-  trigger:
-    project: {{ project }}
-    branch: {{ branch }}
-    strategy: depend
-
-{% if projectneeds.update({ 'project': projectshort }) %}{% endif %}
-{% endfor %}
-{% endif %}
-
-{% if manifest_project is defined %}
-{% set manifest_project_short = manifest_project.split('/')[-1] %}
-{{ manifest_project_short }}:
-  stage: build
-  trigger:
-    project: {{ manifest_project }}
-    branch: {{ branch }}
-    strategy: depend
-
-{% endif %}
diff --git a/manifest-build.yml b/manifest-build.yml
index 697dc69120e1acbfe1161757bf5e8378dcd24a59..6a4381cfc5b457c1bf9e95c583ecec4451760508 100644
--- a/manifest-build.yml
+++ b/manifest-build.yml
@@ -129,3 +129,176 @@ variables:
       .gitlab-ci/scripts/package_release.py \
         --images-dir="${BUILDPATH}/${IMAGEPATH}" \
         --outputdir-local="${DEPLOYPATH_TEST}"
+
+# --------------------------------------------------------------------------------------
+# Stage: build
+# --------------------------------------------------------------------------------------
+.buildbase:
+  tags:
+    - builds
+  timeout: 8h
+  interruptible: true
+  image:
+    name: "${CI_IMAGE_YOCTO}"
+    # Override entrypoint so we can pass --id to set the UID and GID for the
+    # user that is created in the container. This is a feature of the
+    # crops/poky images. See poky-entry.py for details.
+    entrypoint:
+      - "/usr/bin/distro-entry.sh"
+      - "/usr/bin/dumb-init"
+      - "--"
+      - "/usr/bin/poky-entry.py"
+      - "--id=118:998"
+  artifacts:
+    expire_in: 4 weeks
+
+.buildimage:
+  extends:
+    - .buildbase
+    - .build
+  rules:
+    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
+  needs: []
+
+.buildfng:
+  extends:
+    - .buildimage
+  variables:
+    CI_PARAM_IMAGE: ${CI_PARAM_IMAGE_FNG}
+    CI_PARAM_DISTRO: ${CI_PARAM_DISTRO_FNG}
+
+build:merge_request:
+  extends: .infrastructure
+  stage: build
+  timeout: 1h
+  rules:
+    - if: $CI_COMMIT_REF_NAME == $MASTER_BRANCH_MANIFEST && $CI_PIPELINE_SOURCE != "api"
+  script:
+    - cd ${CI_PROJECT_DIR}
+    # Get pipeline for merge request
+    - MR_PIPELINE=$(.gitlab-ci/scripts/get_pipelines.py
+        --gitlab-url=${CI_SERVER_URL}
+        --token=${GITBOT_TOKEN}
+        --project=${CI_PROJECT_PATH}
+        --commit=${CI_COMMIT_SHA}
+        --ref=^${MASTER_BRANCH_MANIFEST} || true | head -1)
+    # If pipeline exists, mirror its result
+    - if [ ! -z "${MR_PIPELINE}" ]; then
+        .gitlab-ci/scripts/mirror_pipeline_result.py
+          --gitlab-url=${CI_SERVER_URL}
+          --token=${GITBOT_TOKEN}
+          --project=${CI_PROJECT_PATH}
+          --pipeline=${MR_PIPELINE}
+    # If no pipeline found, trigger a new one on the master
+    - else
+        .gitlab-ci/scripts/trigger_pipeline.py
+          --gitlab-url=${CI_SERVER_URL}
+          --token=${GITBOT_TOKEN}
+          --project=${CI_PROJECT_PATH}
+          --ref=${MASTER_BRANCH_MANIFEST}
+    - fi
+
+.buildsdk:
+  extends:
+    - .buildimage
+    - .package
+  stage: build
+  rules:
+    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
+      when: manual
+      allow_failure: true
+  variables:
+    BITBAKE_TASK: "populate_sdk"
+  artifacts:
+    paths:
+      - "${BUILDPATH}/${SDKPATH}/*.manifest"
+      - "${BUILDPATH}/${SDKPATH}/*.json"
+    reports:
+      dotenv: package.env
+
+# ---------------------------------------------------------------------------------------
+# Stage: test
+# ---------------------------------------------------------------------------------------
+.test:
+  extends:
+    - .infrastructure
+    - .prepare_test
+  timeout: 1h
+  rules:
+    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
+      when: manual
+      allow_failure: true
+  variables:
+    # Include git submodules
+    GIT_SUBMODULE_STRATEGY: recursive
+    CI_PARAM_TEST_SUITE: '{platform}.jinja2'
+    CI_PARAM_EXTRA: --nop
+  artifacts:
+    when: always
+    paths:
+      - "results/**"
+    reports:
+      junit: results/results-*.xml
+  after_script:
+    - rm -r "${DEPLOYPATH_TEST}"
+
+  script:
+    - |-
+      # Submit tests to lava server
+      RELEASE=$(ls ${DEPLOYPATH_TEST}/)
+      INSTALLSCRIPT_ABS="$DEPLOYPATH_TEST/$RELEASE/$CI_PARAM_MACHINE/fng-install.sh"
+      FNG_INSTALL_URL="${ARTIFACTS_HOST_URL}/${INSTALLSCRIPT_ABS#/*/}"
+      .gitlab-ci/scripts/submit_test.py \
+          --fng-install "${FNG_INSTALL_URL}" \
+          --name \
+          "Gitlab build test ${CI_PARAM_MACHINE} ${RELEASE} ${CI_PIPELINE_ID}" \
+          --results-path "results" \
+          --test-repo ${TESTS_GIT_URL} \
+          --test-repo-branch ${MASTER_BRANCH_MANIFEST} \
+          --test-plan ${CI_PARAM_TEST_SUITE} \
+          ${CI_PARAM_EXTRA} \
+          ${CI_PARAM_PLATFORMS}
+
+# --------------------------------------------------------------------------------------
+# Stage: deploy
+# --------------------------------------------------------------------------------------
+.deployimage:
+  extends:
+    - .infrastructure
+    - .package
+  rules:
+    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
+      when: manual
+      allow_failure: true
+  script:
+    # Workaround: We need a command in the script section to be able to run the
+    # after_script section of the package step.
+    - echo
+  artifacts:
+    paths:
+      - release/**/**/*
+    reports:
+      dotenv: package.env
+
+# --------------------------------------------------------------------------------------
+# Stage: uploadftp
+# --------------------------------------------------------------------------------------
+.uploadftp:
+  variables:
+    CI_PARAM_PACKAGE_FTP: "true"
+  extends:
+    - .infrastructure
+    - .package
+  rules:
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  script:
+    # Workaround: We need a command in the script section to be able to run the
+    # after_script section of the package step.
+    - echo
+  timeout: 30m
+
+.uploadsdkftp:
+  variables:
+    ARTIFACTS_SDK_PATH: "$LOCALDIR/$BUILD_MACHINE/sdk"
diff --git a/manifest.yml b/manifest.yml
index 52c32d3a04f3a990164da69836226fd44b0d7a56..ffeb64703b556ea68e441ad72f591c9746426379 100644
--- a/manifest.yml
+++ b/manifest.yml
@@ -3,17 +3,11 @@
 # Global
 # --------------------------------------------------------------------------------------
 include:
-  - local: manifest-build.yml
   - local: common.yml
-  - local: manifest-package.yml
 
 stages:
   - retrigger
   - infrastructure
-  - build
-  - test
-  - deploy
-  - uploadftp
 
 variables:
   # Default image and distro
@@ -26,6 +20,13 @@ variables:
   # distro for the buildfng must be settable from outside of the job.
   CI_PARAM_IMAGE_FNG: fngsystem-image
   CI_PARAM_DISTRO_FNG: guf-fngsystem
+  # List of machines to build images for:
+  CI_PARAM_MACHINES: imx6guf imx6ullguf imx8mguf imx8mpguf
+  # The id of the gitlab project used in the rules section
+  # to not run pipelines in forked projects
+  # Using variable here, to allow override in other projects
+  # including this file
+  MANIFEST_PROJECT_ID: 1725
 
 workflow:
   rules:
@@ -36,7 +37,7 @@ workflow:
       when: never
     # Do not run pipelines on forked projects
     # (use id instead of name because of rename)
-    - if: $CI_PROJECT_ID != "1725"
+    - if: $CI_PROJECT_ID != $MANIFEST_PROJECT_ID
       when: never
     # Do not run pipelines on integration branches
     - if: $CI_COMMIT_REF_NAME =~ /^integrate\/.*/
@@ -75,424 +76,34 @@ retrigger:
         ;
       done
 
-# --------------------------------------------------------------------------------------
-# Stage: infrastructure
-# --------------------------------------------------------------------------------------
-
-changelog:
+generate-build-jobs:
   extends: .infrastructure
   rules:
     - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
-  script: .gitlab-ci/scripts/changelog_generator.py
-              --token=${GITBOT_TOKEN}
-              --branch ${MASTER_BRANCH_MANIFEST}
-              > changelog.md
-  artifacts:
-    expire_in: 4 weeks
-    paths:
-      - "changelog.md"
-
-# --------------------------------------------------------------------------------------
-# Stage: build
-# --------------------------------------------------------------------------------------
-.buildbase:
-  tags:
-    - builds
-  timeout: 8h
-  interruptible: true
-  image:
-    name: "${CI_IMAGE_YOCTO}"
-    # Override entrypoint so we can pass --id to set the UID and GID for the
-    # user that is created in the container. This is a feature of the
-    # crops/poky images. See poky-entry.py for details.
-    entrypoint:
-      - "/usr/bin/distro-entry.sh"
-      - "/usr/bin/dumb-init"
-      - "--"
-      - "/usr/bin/poky-entry.py"
-      - "--id=118:998"
-  artifacts:
-    expire_in: 4 weeks
-
-.buildimage:
-  extends:
-    - .buildbase
-    - .build
-  rules:
-    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
-  needs: []
-
-.buildfng:
-  extends:
-    - .buildimage
-  variables:
-    CI_PARAM_IMAGE: ${CI_PARAM_IMAGE_FNG}
-    CI_PARAM_DISTRO: ${CI_PARAM_DISTRO_FNG}
-
-build:merge_request:
-  extends: .infrastructure
-  stage: build
-  timeout: 1h
-  rules:
-    - if: $CI_COMMIT_REF_NAME == $MASTER_BRANCH_MANIFEST && $CI_PIPELINE_SOURCE != "api"
   script:
-    - cd ${CI_PROJECT_DIR}
-    # Get pipeline for merge request
-    - MR_PIPELINE=$(.gitlab-ci/scripts/get_pipelines.py
-        --gitlab-url=${CI_SERVER_URL}
-        --token=${GITBOT_TOKEN}
-        --project=${CI_PROJECT_PATH}
-        --commit=${CI_COMMIT_SHA}
-        --ref=^${MASTER_BRANCH_MANIFEST} || true | head -1)
-    # If pipeline exists, mirror its result
-    - if [ ! -z "${MR_PIPELINE}" ]; then
-        .gitlab-ci/scripts/mirror_pipeline_result.py
-          --gitlab-url=${CI_SERVER_URL}
-          --token=${GITBOT_TOKEN}
-          --project=${CI_PROJECT_PATH}
-          --pipeline=${MR_PIPELINE}
-    # If no pipeline found, trigger a new one on the master
-    - else
-        .gitlab-ci/scripts/trigger_pipeline.py
-          --gitlab-url=${CI_SERVER_URL}
-          --token=${GITBOT_TOKEN}
-          --project=${CI_PROJECT_PATH}
-          --ref=${MASTER_BRANCH_MANIFEST}
-    - fi
-
-.buildsdk:
-  extends:
-    - .buildimage
-    - .package
-  stage: build
-  rules:
-    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
-      when: manual
-      allow_failure: true
-  variables:
-    BITBAKE_TASK: "populate_sdk"
+    - .gitlab-ci/scripts/generate_job_from_template.py
+              --template=.gitlab-ci/build-jobs.jinja2
+              --distro=$CI_PARAM_DISTRO
+              --yocto_image=$CI_PARAM_IMAGE
+              --distro_fng=$CI_PARAM_DISTRO_FNG
+              --fngsystem_image=$CI_PARAM_IMAGE_FNG
+              --gitlab_ci_current_rev=$GITLAB_CI_CURRENT_REV
+              --ci_project_root_namespace=$CI_PROJECT_ROOT_NAMESPACE
+              --master_branch_manifest=$MASTER_BRANCH_MANIFEST
+              $CI_PARAM_MACHINES
+              > build-jobs.yml
   artifacts:
+    expire_in: 4 weeks
     paths:
-      - "${BUILDPATH}/${SDKPATH}/*.manifest"
-      - "${BUILDPATH}/${SDKPATH}/*.json"
-    reports:
-      dotenv: package.env
+      - build-jobs.yml
 
-# ---------------------------------------------------------------------------------------
-# Stage: test
-# ---------------------------------------------------------------------------------------
-.test:
-  extends:
-    - .infrastructure
-    - .prepare_test
-  timeout: 1h
+trigger-build-jobs:
+  stage: infrastructure
+  needs: ["generate-build-jobs"]
   rules:
     - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
-      when: manual
-      allow_failure: true
-  variables:
-    # Include git submodules
-    GIT_SUBMODULE_STRATEGY: recursive
-    CI_PARAM_TEST_SUITE: '{platform}.jinja2'
-    CI_PARAM_EXTRA: --nop
-  artifacts:
-    when: always
-    paths:
-      - "results/**"
-    reports:
-      junit: results/results-*.xml
-  after_script:
-    - rm -r "${DEPLOYPATH_TEST}"
-
-  script:
-    - |-
-      # Submit tests to lava server
-      RELEASE=$(ls ${DEPLOYPATH_TEST}/)
-      INSTALLSCRIPT_ABS="$DEPLOYPATH_TEST/$RELEASE/$CI_PARAM_MACHINE/fng-install.sh"
-      FNG_INSTALL_URL="${ARTIFACTS_HOST_URL}/${INSTALLSCRIPT_ABS#/*/}"
-      .gitlab-ci/scripts/submit_test.py \
-          --fng-install "${FNG_INSTALL_URL}" \
-          --name \
-          "Gitlab build test ${CI_PARAM_MACHINE} ${RELEASE} ${CI_PIPELINE_ID}" \
-          --results-path "results" \
-          --test-repo ${TESTS_GIT_URL} \
-          --test-repo-branch ${MASTER_BRANCH_MANIFEST} \
-          --test-plan ${CI_PARAM_TEST_SUITE} \
-          ${CI_PARAM_EXTRA} \
-          ${CI_PARAM_PLATFORMS}
-
-# --------------------------------------------------------------------------------------
-# Stage: deploy
-# --------------------------------------------------------------------------------------
-.deployimage:
-  extends:
-    - .infrastructure
-    - .package
-  rules:
-    - if: $CI_COMMIT_REF_NAME != $MASTER_BRANCH_MANIFEST || $CI_PIPELINE_SOURCE == "api"
-      when: manual
-      allow_failure: true
-  script:
-    # Workaround: We need a command in the script section to be able to run the
-    # after_script section of the package step.
-    - echo
-  artifacts:
-    paths:
-      - release/**/**/*
-    reports:
-      dotenv: package.env
-
-# --------------------------------------------------------------------------------------
-# Stage: uploadftp
-# --------------------------------------------------------------------------------------
-.uploadftp:
-  variables:
-    CI_PARAM_PACKAGE_FTP: "true"
-  extends:
-    - .infrastructure
-    - .package
-  rules:
-    - if: $CI_COMMIT_TAG
-      when: manual
-      allow_failure: true
-  script:
-    # Workaround: We need a command in the script section to be able to run the
-    # after_script section of the package step.
-    - echo
-  timeout: 30m
-
-.uploadsdkftp:
-  variables:
-    ARTIFACTS_SDK_PATH: "$LOCALDIR/$BUILD_MACHINE/sdk"
-
-# ---------------------------------------------------------------------------------------
-# Actual jobs
-# ---------------------------------------------------------------------------------------
-
-build:imx6guf:
-  extends: .buildimage
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6guf
-
-build:imx6ullguf:
-  extends: .buildimage
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6ullguf
-
-build:imx8mguf:
-  extends: .buildimage
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mguf
-
-build:imx8mpguf:
-  extends: .buildimage
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mpguf
-
-build:imx6guf:fngsystem:
-  extends: .buildfng
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6guf
-
-build:imx6ullguf:fngsystem:
-  extends: .buildfng
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6ullguf
-
-build:imx8mguf:fngsystem:
-  extends: .buildfng
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mguf
-
-build:imx8mpguf:fngsystem:
-  extends: .buildfng
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mpguf
-
-# -------------------------------------------------------------------------------------
-
-buildsdk:imx6guf:
-  extends: .buildsdk
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6guf
-
-buildsdk:imx6ullguf:
-  extends: .buildsdk
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx6ullguf
-
-buildsdk:imx8mguf:
-  extends: .buildsdk
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mguf
-
-buildsdk:imx8mpguf:
-  extends: .buildsdk
-  stage: build
-  variables:
-    CI_PARAM_MACHINE: imx8mpguf
-
-# -------------------------------------------------------------------------------------
-
-deployimage:imx6guf:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx6guf", "changelog"]
-
-deployimage:imx6ullguf:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx6ullguf", "changelog"]
-
-deployimage:imx8mguf:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx8mguf", "changelog"]
-
-deployimage:imx8mpguf:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx8mpguf", "changelog"]
-
-deployimage:imx6guf:fngsystem:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx6guf:fngsystem", "changelog"]
-
-deployimage:imx6ullguf:fngsystem:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx6ullguf:fngsystem", "changelog"]
-
-deployimage:imx8mguf:fngsystem:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx8mguf:fngsystem", "changelog"]
-
-deployimage:imx8mpguf:fngsystem:
-  extends: .deployimage
-  stage: deploy
-  needs: ["build:imx8mpguf:fngsystem", "changelog"]
-
-# -------------------------------------------------------------------------------------
-
-uploadftp:imx6guf:
-  extends:
-    - .uploadftp
-    - .uploadsdkftp
-  stage: uploadftp
-  needs: ["build:imx6guf", "buildsdk:imx6guf", "changelog"]
-
-uploadftp:imx6ullguf:
-  extends:
-    - .uploadftp
-    - .uploadsdkftp
-  stage: uploadftp
-  needs: ["build:imx6ullguf", "buildsdk:imx6ullguf", "changelog"]
-
-uploadftp:imx8mguf:
-  extends:
-    - .uploadftp
-    - .uploadsdkftp
-  stage: uploadftp
-  needs: ["build:imx8mguf", "buildsdk:imx8mguf", "changelog"]
-
-uploadftp:imx8mpguf:
-  extends:
-    - .uploadftp
-    - .uploadsdkftp
-  stage: uploadftp
-  needs: ["build:imx8mpguf", "buildsdk:imx8mpguf", "changelog"]
-
-uploadftp:imx6guf:fngsystem:
-  extends: .uploadftp
-  stage: uploadftp
-  needs: ["build:imx6guf:fngsystem", "changelog"]
-
-uploadftp:imx6ullguf:fngsystem:
-  extends: .uploadftp
-  stage: uploadftp
-  needs: ["build:imx6ullguf:fngsystem", "changelog"]
-
-uploadftp:imx8mguf:fngsystem:
-  extends: .uploadftp
-  stage: uploadftp
-  needs: ["build:imx8mguf:fngsystem", "changelog"]
-
-uploadftp:imx8mpguf:fngsystem:
-  extends: .uploadftp
-  stage: uploadftp
-  needs: ["build:imx8mpguf:fngsystem", "changelog"]
-
-# -------------------------------------------------------------------------------------
-
-platformtest:imx6guf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx6guf
-  variables:
-    CI_PARAM_MACHINE: imx6guf
-    CI_PARAM_PLATFORMS: santaro santoka santino santino-lt
-
-platformtest:imx6ullguf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx6ullguf
-  variables:
-    CI_PARAM_MACHINE: imx6ullguf
-    CI_PARAM_PLATFORMS: nallino
-
-platformtest:imx8mguf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx6ullguf
-  variables:
-    CI_PARAM_MACHINE: imx8mguf
-    CI_PARAM_PLATFORMS: tanaro
-
-smoketest:imx6guf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx6guf
-  variables:
-    CI_PARAM_MACHINE: imx6guf
-    CI_PARAM_PLATFORMS: imx6guf
-    CI_PARAM_TEST_SUITE: boot.jinja2
-    CI_PARAM_EXTRA: --all-devices
-
-smoketest:imx6ullguf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx6ullguf
-  variables:
-    CI_PARAM_MACHINE: imx6ullguf
-    CI_PARAM_PLATFORMS: imx6ullguf
-    CI_PARAM_TEST_SUITE: boot.jinja2
-    CI_PARAM_EXTRA: --all-devices
-
-smoketest:imx8mguf:
-  extends: .test
-  stage: test
-  needs:
-    - job: build:imx8mguf
-  variables:
-    CI_PARAM_MACHINE: imx8mguf
-    CI_PARAM_PLATFORMS: imx8mguf
-    CI_PARAM_TEST_SUITE: boot.jinja2
-    CI_PARAM_EXTRA: --all-devices
+  trigger:
+    include:
+      - artifact: build-jobs.yml
+        job: generate-build-jobs
+    strategy: depend