summaryrefslogtreecommitdiff
path: root/.github
diff options
context:
space:
mode:
authorNick Winans <nick.win999@gmail.com>2022-02-02 23:15:50 -0600
committerGitHub <noreply@github.com>2022-02-03 00:15:50 -0500
commitedbbbc75409c06f7828b8fd70323a2a3350980e6 (patch)
tree36057a52c8a2b5dd822826c63f9b9f590136d0c6 /.github
parent74307504280573aae5819afad29aceaf97b8344e (diff)
feat(ci): Use metadata for builds
* Build per board * Nightly builds of all boards * Detect board changes and build those changed * Core set of boards/shields built otherwise.
Diffstat (limited to '.github')
-rw-r--r--.github/workflows/build.yml447
1 files changed, 361 insertions, 86 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b790b66..bfb195f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,75 +9,19 @@ on:
paths:
- ".github/workflows/build.yml"
- "app/**"
+ schedule:
+ - cron: '22 4 * * *'
jobs:
build:
+ if: ${{ always() }}
runs-on: ubuntu-latest
container:
image: docker.io/zmkfirmware/zmk-build-arm:2.5
+ needs: compile-matrix
strategy:
matrix:
- board:
- - bluemicro840_v1
- - nice_nano
- - nice_nano_v2
- - nrfmicro_13
- - proton_c
- shield:
- - bfo9000_left
- - bfo9000_right
- - boardsource3x4
- - corne_left
- - corne_right
- - cradio_left
- - cradio_right
- - crbn
- - eek
- - helix_left
- - helix_right
- - iris_left
- - iris_right
- - jian_left
- - jian_right
- - jorne_left
- - jorne_right
- - kyria_left
- - kyria_right
- - lily58_left
- - lily58_right
- - microdox_left
- - microdox_right
- - nibble
- - qaz
- - quefrency_left
- - quefrency_right
- - reviung41
- - romac
- - romac_plus
- - settings_reset
- - sofle_left
- - sofle_right
- - splitreus62_left
- - splitreus62_right
- - tg4x
- - tidbit
- cmake-args: [""]
- include:
- - board: bdn9_rev2
- - board: dz60rgb_rev1
- - board: nrf52840_m2
- shield: m60
- - board: planck_rev6
- - board: proton_c
- shield: clueboard_california
- - board: nice_nano_v2
- shield: kyria_left
- cmake-args: -DCONFIG_ZMK_DISPLAY=y
- skip-archive: true
- - board: nice_nano_v2
- shield: kyria_right
- cmake-args: -DCONFIG_ZMK_DISPLAY=y
- skip-archive: true
+ include: ${{ fromJSON(needs.compile-matrix.outputs.include-list) }}
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -104,29 +48,360 @@ jobs:
run: west update
- name: Export Zephyr CMake package (west zephyr-export)
run: west zephyr-export
- - name: Prepare variables
- id: variables
- run: |
- SHIELD_ARG=
- ARTIFACT_NAME="${{ matrix.board }}"
-
- if [ -n "${{ matrix.shield }}" ]; then
- SHIELD_ARG="-DSHIELD=${{ matrix.shield }}"
- ARTIFACT_NAME="${ARTIFACT_NAME}-${{ matrix.shield }}"
- fi
-
- ARTIFACT_NAME="${ARTIFACT_NAME}-zmk"
-
- echo ::set-output name=shield-arg::${SHIELD_ARG}
- echo ::set-output name=artifact-name::${ARTIFACT_NAME}
- - name: Build (west build)
- run: west build -s app -b ${{ matrix.board }} -- ${{ steps.variables.outputs.shield-arg }} ${{ matrix.cmake-args }}
- - name: Archive artifacts
- if: ${{ !matrix.skip-archive }}
- uses: actions/upload-artifact@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v2
with:
- name: "${{ steps.variables.outputs.artifact-name }}"
- path: |
- build/zephyr/zmk.hex
- build/zephyr/zmk.uf2
- continue-on-error: true
+ node-version: '14.x'
+ - name: Install @actions/artifact
+ run: npm install @actions/artifact
+ - name: Build and upload artifacts
+ uses: actions/github-script@v4
+ id: boards-list
+ with:
+ script: |
+ const fs = require('fs');
+ const artifact = require('@actions/artifact');
+ const artifactClient = artifact.create();
+
+ const execSync = require('child_process').execSync;
+
+ const buildShieldArgs = JSON.parse(`${{ matrix.shieldArgs }}`);
+
+ let error = false;
+
+ for (const shieldArgs of buildShieldArgs) {
+ try {
+ const output = execSync(`west build -s app -p -b ${{ matrix.board }} -- ${shieldArgs.shield ? '-DSHIELD=' + shieldArgs.shield : ''} ${shieldArgs['cmake-args'] || ''}`);
+
+ console.log(`::group::${{ matrix.board}} ${shieldArgs.shield} Build`)
+ console.log(output.toString());
+
+ const fileExtensions = ["hex", "uf2"];
+
+ const files = fileExtensions
+ .map(extension => "build/zephyr/zmk." + extension)
+ .filter(path => fs.existsSync(path));
+
+ const rootDirectory = 'build/zephyr';
+ const options = {
+ continueOnError: true
+ }
+
+ const cmakeName = shieldArgs['cmake-args'] ? '-' + (shieldArgs.nickname || shieldArgs['cmake-args'].split(' ').join('')) : '';
+ const artifactName = `${{ matrix.board }}${shieldArgs.shield ? '-' + shieldArgs.shield : ''}${cmakeName}-zmk`;
+
+ await artifactClient.uploadArtifact(artifactName, files, rootDirectory, options);
+ } catch (e) {
+ console.error(`::error::Failed to build or upload ${{ matrix.board }} ${shieldArgs.shield} ${shieldArgs['cmake-args']}`);
+ console.error(e);
+ error = true;
+ } finally {
+ console.log('::endgroup::');
+ }
+ }
+
+ if (error) {
+ throw new Error('Failed to build one or more configurations');
+ }
+ compile-matrix:
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ needs: [core-coverage, board-changes, nightly]
+ outputs:
+ include-list: ${{ steps.compile-list.outputs.result }}
+ steps:
+ - name: Join build lists
+ uses: actions/github-script@v4
+ id: compile-list
+ with:
+ script: |
+ const coreCoverage = `${{ needs.core-coverage.outputs.core-include }}` || "[]";
+ const boardChanges = `${{ needs.board-changes.outputs.boards-include }}` || "[]";
+ const nightly = `${{ needs.nightly.outputs.nightly-include }}` || "[]";
+
+ const combined = [
+ ...JSON.parse(coreCoverage),
+ ...JSON.parse(boardChanges),
+ ...JSON.parse(nightly)
+ ];
+ const combinedUnique = [...new Map(combined.map(el => [JSON.stringify(el), el])).values()];
+
+ const perBoard = {};
+
+ for (const configuration of combinedUnique) {
+ if (!perBoard[configuration.board])
+ perBoard[configuration.board] = [];
+
+ perBoard[configuration.board].push({
+ shield: configuration.shield,
+ 'cmake-args': configuration['cmake-args'],
+ nickname: configuration.nickname
+ })
+ }
+
+ return Object.entries(perBoard).map(([board, shieldArgs]) => ({
+ board,
+ shieldArgs: JSON.stringify(shieldArgs),
+ }));
+ core-coverage:
+ if: ${{ needs.get-changed-files.outputs.core-changes == 'true' }}
+ runs-on: ubuntu-latest
+ needs: get-changed-files
+ outputs:
+ core-include: ${{ steps.core-list.outputs.result }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '14.x'
+ - name: Install js-yaml
+ run: npm install js-yaml
+ - uses: actions/github-script@v4
+ id: core-list
+ with:
+ script: |
+ const fs = require('fs');
+ const yaml = require('js-yaml');
+
+ const coreCoverage = yaml.load(fs.readFileSync('app/core-coverage.yml', 'utf8'));
+
+ let include = coreCoverage.board.flatMap(board =>
+ coreCoverage.shield.map(shield => ({ board, shield }))
+ );
+
+ return [...include, ...coreCoverage.include];
+ board-changes:
+ if: ${{ needs.get-changed-files.outputs.board-changes == 'true' }}
+ runs-on: ubuntu-latest
+ needs: [get-grouped-hardware, get-changed-files]
+ outputs:
+ boards-include: ${{ steps.boards-list.outputs.result }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '14.x'
+ - name: Install js-yaml
+ run: npm install js-yaml
+ - uses: actions/github-script@v4
+ id: boards-list
+ with:
+ script: |
+ const fs = require('fs');
+ const yaml = require('js-yaml');
+
+ const changedFiles = JSON.parse(`${{ needs.get-changed-files.outputs.changed-files }}`);
+ const metadata = JSON.parse(`${{ needs.get-grouped-hardware.outputs.organized-metadata }}`);
+ const boardChanges = new Set(changedFiles.filter(f => f.startsWith('app/boards')).map(f => f.split('/').slice(0, 4).join('/')));
+
+ return (await Promise.all([...boardChanges].flatMap(async bc => {
+ const globber = await glob.create(bc + "/*.zmk.yml");
+ const files = await globber.glob();
+
+ const aggregated = files.flatMap((f) =>
+ yaml.loadAll(fs.readFileSync(f, "utf8"))
+ );
+
+ const boardAndShield = (b, s) => {
+ if (s.siblings) {
+ return s.siblings.map(shield => ({
+ board: b.id,
+ shield,
+ }));
+ } else {
+ return {
+ board: b.id,
+ shield: s.id
+ };
+ }
+ }
+
+ return aggregated.flatMap(hm => {
+ switch (hm.type) {
+ case "board":
+ if (hm.features && hm.features.includes("keys")) {
+ if (hm.siblings) {
+ return hm.siblings.map(board => ({
+ board,
+ }));
+ } else {
+ return {
+ board: hm.id
+ };
+ }
+ } else if (hm.exposes) {
+ return hm.exposes.flatMap(i =>
+ metadata.interconnects[i].shields.flatMap(s => boardAndShield(hm, s))
+ );
+ } else {
+ console.error("Board without keys or interconnect");
+ }
+ break;
+ case "shield":
+ if (hm.features && hm.features.includes("keys")) {
+ return hm.requires.flatMap(i =>
+ metadata.interconnects[i].boards.flatMap(b => boardAndShield(b, hm))
+ );
+ }
+ break;
+ case "interconnect":
+ break;
+ }
+ });
+ }))).flat();
+ nightly:
+ if: ${{ github.event_name == 'schedule' }}
+ runs-on: ubuntu-latest
+ needs: get-grouped-hardware
+ outputs:
+ nightly-include: ${{ steps.nightly-list.outputs.result }}
+ steps:
+ - name: Create nightly list
+ uses: actions/github-script@v4
+ id: nightly-list
+ with:
+ script: |
+ const metadata = JSON.parse(`${{ needs.get-grouped-hardware.outputs.organized-metadata }}`);
+
+ let includeOnboard = metadata.onboard.flatMap(b => {
+ if (b.siblings) {
+ return b.siblings.map(board => ({
+ board,
+ }));
+ } else {
+ return {
+ board: b.id,
+ };
+ }
+ });
+
+ let includeInterconnect = Object.values(metadata.interconnects).flatMap(i =>
+ i.boards.flatMap(b =>
+ i.shields.flatMap(s => {
+ if (s.siblings) {
+ return s.siblings.map(shield => ({
+ board: b.id,
+ shield,
+ }));
+ } else {
+ return {
+ board: b.id,
+ shield: s.id,
+ };
+ }
+ })
+ )
+ );
+
+ return [...includeOnboard, ...includeInterconnect];
+ get-grouped-hardware:
+ runs-on: ubuntu-latest
+ outputs:
+ organized-metadata: ${{ steps.organize-metadata.outputs.result }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '14.x'
+ - name: Install js-yaml
+ run: npm install js-yaml
+ - name: Aggregate Metadata
+ uses: actions/github-script@v4
+ id: aggregate-metadata
+ with:
+ script: |
+ const fs = require('fs');
+ const yaml = require('js-yaml');
+
+ const globber = await glob.create("app/boards/**/*.zmk.yml");
+ const files = await globber.glob();
+
+ const aggregated = files.flatMap((f) =>
+ yaml.loadAll(fs.readFileSync(f, "utf8"))
+ );
+
+ return JSON.stringify(aggregated).replace(/\\/g,"\\\\");
+ result-encoding: string
+
+ - name: Organize Metadata
+ uses: actions/github-script@v4
+ id: organize-metadata
+ with:
+ script: |
+ const hardware = JSON.parse(`${{ steps.aggregate-metadata.outputs.result }}`);
+
+ const grouped = hardware.reduce((agg, hm) => {
+ switch (hm.type) {
+ case "board":
+ if (hm.features && hm.features.includes("keys")) {
+ agg.onboard.push(hm);
+ } else if (hm.exposes) {
+ hm.exposes.forEach((element) => {
+ let ic = agg.interconnects[element] || {
+ boards: [],
+ shields: [],
+ };
+ ic.boards.push(hm);
+ agg.interconnects[element] = ic;
+ });
+ } else {
+ console.error("Board without keys or interconnect");
+ }
+ break;
+ case "shield":
+ if (hm.features && hm.features.includes("keys")) {
+ hm.requires.forEach((id) => {
+ let ic = agg.interconnects[id] || { boards: [], shields: [] };
+ ic.shields.push(hm);
+ agg.interconnects[id] = ic;
+ });
+ }
+ break;
+ case "interconnect":
+ let ic = agg.interconnects[hm.id] || { boards: [], shields: [] };
+ ic.interconnect = hm;
+ agg.interconnects[hm.id] = ic;
+ break;
+ }
+ return agg;
+ },
+ { onboard: [], interconnects: {} });
+
+ return JSON.stringify(grouped).replace(/\\/g,"\\\\");
+ result-encoding: string
+ get-changed-files:
+ if: ${{ github.event_name != 'schedule' }}
+ runs-on: ubuntu-latest
+ outputs:
+ changed-files: ${{ steps.changed-files.outputs.all }}
+ board-changes: ${{ steps.board-changes.outputs.result }}
+ core-changes: ${{ steps.core-changes.outputs.result }}
+ steps:
+ - uses: Ana06/get-changed-files@v2.0.0
+ id: changed-files
+ with:
+ format: 'json'
+ - uses: actions/github-script@v4
+ id: board-changes
+ with:
+ script: |
+ const changedFiles = JSON.parse(`${{ steps.changed-files.outputs.all }}`);
+ const boardChanges = changedFiles.filter(f => f.startsWith('app/boards'));
+ return boardChanges.length ? 'true' : 'false';
+ result-encoding: string
+ - uses: actions/github-script@v4
+ id: core-changes
+ with:
+ script: |
+ const changedFiles = JSON.parse(`${{ steps.changed-files.outputs.all }}`);
+ const boardChanges = changedFiles.filter(f => f.startsWith('app/boards'));
+ const appChanges = changedFiles.filter(f => f.startsWith('app'));
+ const ymlChanges = changedFiles.includes('.github/workflows/build.yml');
+ return boardChanges.length < appChanges.length || ymlChanges ? 'true' : 'false';
+ result-encoding: string