diff --git a/.github/actions/check-code/action.yml b/.github/actions/check-code/action.yml deleted file mode 100644 index 77e9921..0000000 --- a/.github/actions/check-code/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Should only be used in a job where MATLAB and MatBox is installed in previous steps - -name: 'Check code and upload report' -description: 'Analyse code for code issues and upload code issues report to GitHub' - -inputs: - code_directory: - description: 'Which folder to run the code check on' - required: false - default: './code' - tools_directory: - description: 'Where the codecheckToolbox function is located' - required: false - default: './tools' - -runs: - using: "composite" - steps: - - name: Check for MATLAB code issues - uses: matlab-actions/run-command@v2 - if: always() - with: - command: | - addpath(genpath("${{ inputs.tools_directory }}")); - if exist("codecheckToolbox", "file") - codecheckToolbox(); - else - matbox.tasks.codecheckToolbox(pwd, ... - "FoldersToCheck", "${{ inputs.code_directory }}"); - end - - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v3 - continue-on-error: true # Will fail for MATLAB release < R2023a - with: - sarif_file: docs/reports/code_issues.sarif diff --git a/.github/actions/install-matbox/action.yml b/.github/actions/install-matbox/action.yml deleted file mode 100644 index 0a72a35..0000000 --- a/.github/actions/install-matbox/action.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: 'Install MatBox' -description: 'Install MatBox in MATLAB on GitHub runner' -inputs: - mode: # mode of installation: release or commit, i.e install latest released version or use latest commit - description: 'Which installation mode to use' - required: true - default: 'commit' -runs: - using: "composite" - steps: - - name: Install MatBox - uses: matlab-actions/run-command@v2 - with: - command: | - addpath( "${{ github.action_path }}" ) - installMatBox( "${{ inputs.mode }}" ) - - - name: Confirm installation of MatBox - uses: matlab-actions/run-command@v2 - with: - command: | - versionStr = matbox.toolboxversion(); - assert(~isempty(versionStr), 'Failed to install MatBox.'); diff --git a/.github/actions/install-matbox/installMatBox.m b/.github/actions/install-matbox/installMatBox.m deleted file mode 100644 index 68223be..0000000 --- a/.github/actions/install-matbox/installMatBox.m +++ /dev/null @@ -1,45 +0,0 @@ -function installMatBox(mode) -% installMatBox - Install MatBox from latest release or latest commit - - arguments - mode (1,1) string {mustBeMember(mode, ["release", "commit"])} = "release" - end - - if mode == "release" - installFromRelease() % local function - elseif mode == "commit" - installFromCommit() % local function - end -end - -function installFromRelease() - addonsTable = matlab.addons.installedAddons(); - isMatchedAddon = addonsTable.Name == "MatBox"; - - if ~isempty(isMatchedAddon) && any(isMatchedAddon) - matlab.addons.enableAddon('MatBox') - else - info = webread('https://api.github.com/repos/ehennestad/MatBox/releases/latest'); - assetNames = {info.assets.name}; - isMltbx = startsWith(assetNames, 'MatBox'); - - mltbx_URL = info.assets(isMltbx).browser_download_url; - - % Download matbox - tempFilePath = websave(tempname, mltbx_URL); - cleanupObj = onCleanup(@(fp) delete(tempFilePath)); - - % Install toolbox - matlab.addons.install(tempFilePath); - end -end - -function installFromCommit() - - scriptPath = mfilename('fullpath'); - projectFolder = extractBefore(scriptPath, fullfile('.github', 'actions')); - codeDirectory = fullfile(projectFolder, 'code'); - - addpath(genpath(codeDirectory)) - savepath() -end diff --git a/.github/actions/push-badges/action.yml b/.github/actions/push-badges/action.yml deleted file mode 100644 index 67df8b9..0000000 --- a/.github/actions/push-badges/action.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: 'Push GitHub badges' -description: 'Push badges for use on GitHub and located in .github/badges if there are detected changes.' - -inputs: - pr-ref: - description: "Branch ref to check out" - required: true - pr-repo: - description: "Repository (owner/repo) to check out from" - required: true - -runs: - using: "composite" - steps: - # Check out the actual source branch in a separate working tree - - name: Checkout PR branch for pushing badges - uses: actions/checkout@v4 - with: - ref: ${{ inputs.pr-ref }} - repository: ${{ inputs.pr-repo }} - path: pr-branch - - # Copy generated files to actual branch - - name: Copy badges to PR branch - shell: bash - run: | - cp -r .github/badges/* pr-branch/.github/badges/ 2>/dev/null || echo "No badges to copy" - - # Commit updated SVG badges for the issues and tests (if changed) - - name: Commit and push SVG badges if updated - working-directory: pr-branch - shell: bash - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git fetch - - if [[ $(git add .github/badges/* --dry-run | wc -l) -gt 0 ]]; then - git add .github/badges - git commit -m "Update GitHub badges" - git push origin HEAD - else - echo "Nothing to commit" - fi diff --git a/.github/actions/test-code/action.yml b/.github/actions/test-code/action.yml deleted file mode 100644 index 0b784c5..0000000 --- a/.github/actions/test-code/action.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Should only be used in a job where MATLAB and MatBox is installed in previous steps - -name: 'Run toolbox test suites' -description: 'Run the test suites available for a MATLAB toolbox' - -inputs: - code_directory: - description: 'Which folder to run the tests on' - default: './code' - tools_directory: - description: 'Where the testToolbox function is located' - default: './tools' - -runs: - using: "composite" - steps: - - name: Run MATLAB test suites - uses: matlab-actions/run-command@v2 - if: always() - with: - command: | - addpath(genpath("${{ inputs.tools_directory }}")); - if exist("testToolbox", "file") - testToolbox(); - else - matbox.tasks.testToolbox(pwd, ... - "SourceFolderName", "${{ inputs.code_directory }}"); - end - - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: "docs/reports/test-results.xml" diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg index 892f431..6071791 100644 --- a/.github/badges/code_issues.svg +++ b/.github/badges/code_issues.svg @@ -1 +1 @@ -code issuescode issues11 \ No newline at end of file +code issues: 1code issues1 diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index b2979d5..b8630e0 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -teststests23 passed23 passed \ No newline at end of file +tests: 24 passedtests24 passed diff --git a/.github/workflows/reusable_check_code.yml b/.github/workflows/reusable_check_code.yml deleted file mode 100644 index fbc4c19..0000000 --- a/.github/workflows/reusable_check_code.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Analyse code - -on: - workflow_call: - inputs: - code_directory: - description: Path to the directory containing code. Code analysis will run on the contents of this folder and its subfolders. - type: string - default: 'code' - tools_directory: - description: Path to the directory containing CI tools. Used for locating customized MatBox tasks. - type: string - default: 'tools' - matlab_release: - description: MATLAB release to use when running code analysis. - type: string - default: 'latest' - -jobs: - # This workflow contains a single job called "check" - check: - name: Check code - runs-on: ubuntu-latest - - steps: - - name: Check out repo - uses: actions/checkout@v4 - - - name: Set up MATLAB (${{ inputs.matlab_release }}) - uses: matlab-actions/setup-matlab@v2 - with: - release: ${{ inputs.matlab_release }} - cache: true - - - name: Install MatBox - uses: ehennestad/matbox/.github/actions/install-matbox@v0.9 - - - name: Check code and upload report - uses: ehennestad/matbox/.github/actions/check-code@v0.9 - with: - code_directory: ${{ inputs.code_directory }} - tools_directory: ${{ inputs.tools_directory }} - - - name: Commit SVG badges if updated - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - uses: ehennestad/matbox/.github/actions/push-badges@v0.9 - with: - pr-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} - pr-repo: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }} diff --git a/.github/workflows/reusable_test_workflow.yml b/.github/workflows/reusable_test_workflow.yml deleted file mode 100644 index 8107f3d..0000000 --- a/.github/workflows/reusable_test_workflow.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Run tests - -on: - workflow_call: - inputs: - code_directory: - description: Path to the directory containing code. Code coverage and code analysis will run on the contents of this folder and its subfolders. - type: string - default: 'code' - tools_directory: - description: Path to the directory containing CI tools or tests. Used for locating unit tests and running customized MatBox tasks. - type: string - default: 'tools' - matlab_release: - description: MATLAB release to use for running test suites and code analysis. - type: string - default: 'latest' - matlab_use_cache: - description: Whether to cache the MATLAB installation for faster subsequent setups. - type: boolean - default: false - matlab_products: - description: Optional list of MATLAB products to install. - type: string - default: '' - - secrets: - CODECOV_TOKEN: - required: false - -jobs: - # This workflow contains a single job called "test" - test: - name: Test toolbox code - # The type of runner that the job will run on - runs-on: ubuntu-latest - env: # Assign secret to env to be able to use it in "if:" conditional below - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up MATLAB (${{ inputs.matlab_release }}) - uses: matlab-actions/setup-matlab@v2 - with: - release: ${{ inputs.matlab_release }} - cache: ${{ inputs.matlab_use_cache }} - products: ${{ inputs.matlab_products }} - - - name: Install MatBox - uses: ehennestad/matbox/.github/actions/install-matbox@v0.9 - - - name: Check code and upload report - uses: ehennestad/matbox/.github/actions/check-code@v0.9 - with: - code_directory: ${{ inputs.code_directory }} - tools_directory: ${{ inputs.tools_directory }} - - - name: Run tests - uses: ehennestad/matbox/.github/actions/test-code@v0.9 - with: - code_directory: ${{ inputs.code_directory }} - tools_directory: ${{ inputs.tools_directory }} - - - name: Commit SVG badges if updated - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - uses: ehennestad/matbox/.github/actions/push-badges@v0.9 - with: - pr-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} - pr-repo: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }} - - - name: Upload code coverage report to Codecov - uses: codecov/codecov-action@v5 - if: ${{ env.CODECOV_TOKEN != '' }} - with: - token: ${{ env.CODECOV_TOKEN }} - files: docs/reports/codecoverage.xml - - - name: Save reports directory - uses: actions/upload-artifact@v4 - if: always() - with: - name: reports - path: docs/reports diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index cc4122c..a250cdd 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,8 +27,11 @@ concurrency: jobs: call-workflow-test-toolbox: name: Test toolbox code - uses: ehennestad/matbox/.github/workflows/reusable_test_workflow.yml@v0.9 + uses: ehennestad/matbox-actions/.github/workflows/test-code-workflow.yml@v1 with: + source_directory: code + tests_directory: tools/tests + tools_directory: tools matlab_use_cache: true secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/code/+matbox/+tasks/+internal/createBadge.m b/code/+matbox/+tasks/+internal/createBadge.m new file mode 100644 index 0000000..60a1c01 --- /dev/null +++ b/code/+matbox/+tasks/+internal/createBadge.m @@ -0,0 +1,27 @@ +function createBadge(label, message, color, projectRootDirectory) +% createBadge - Write a badge as JSON or as a legacy SVG file +% +% Writes a badge description as a JSON file (docs/reports/badges) when +% the MATBOX_BADGE_FORMAT environment variable is set to "json". The +% matbox-actions workflows set this variable and render the JSON files +% to SVG badges with badge-maker. +% +% Otherwise falls back to rendering the SVG in MATLAB via the deprecated +% pybadges Python package, for callers without a render step: local runs +% and repositories pinned to older matbox-actions versions. This +% fallback will be removed in a future major release. + + arguments + label (1,1) string + message (1,1) string + color (1,1) string + projectRootDirectory (1,1) string {mustBeFolder} + end + + if strcmpi(getenv("MATBOX_BADGE_FORMAT"), "json") + matbox.utility.writeBadgeJSONFile(label, message, color, ... + "OutputFolder", fullfile(projectRootDirectory, "docs", "reports", "badges")) + else + matbox.utility.createBadgeSvg(label, message, color, projectRootDirectory) + end +end diff --git a/code/+matbox/+tasks/codecheckToolbox.m b/code/+matbox/+tasks/codecheckToolbox.m index 45d6f64..b81c753 100644 --- a/code/+matbox/+tasks/codecheckToolbox.m +++ b/code/+matbox/+tasks/codecheckToolbox.m @@ -92,7 +92,7 @@ function createCodeIssuesBadge(issueCount, projectRootDir) color = "red"; end - matbox.utility.createBadgeSvg("code issues", ... + matbox.tasks.internal.createBadge("code issues", ... string(issueCount.Total), color, projectRootDir) end diff --git a/code/+matbox/+tasks/testToolbox.m b/code/+matbox/+tasks/testToolbox.m index de11b1c..da15d6f 100644 --- a/code/+matbox/+tasks/testToolbox.m +++ b/code/+matbox/+tasks/testToolbox.m @@ -131,7 +131,7 @@ function createTestResultBadge(results, projectRootDirectory) color = "red"; message = sprintf("%d/%d passed", numPassedTests, numTests); end - matbox.utility.createBadgeSvg("tests", message, color, projectRootDirectory) + matbox.tasks.internal.createBadge("tests", message, color, projectRootDirectory) end function displayTestResultSummary(testResults) diff --git a/code/+matbox/+utility/createBadgeSvg.m b/code/+matbox/+utility/createBadgeSvg.m index 82e0aa5..da0999b 100644 --- a/code/+matbox/+utility/createBadgeSvg.m +++ b/code/+matbox/+utility/createBadgeSvg.m @@ -1,4 +1,12 @@ function createBadgeSvg(label, message, color, projectRootDirectory, options) +% createBadgeSvg - Create an SVG badge using the pybadges Python package +% +% Deprecated: when running via current matbox-actions workflows, MatBox +% tasks write badge descriptions as JSON files instead (see +% matbox.utility.writeBadgeJSONFile), which CI renders to SVG with +% badge-maker. This function remains as a fallback for local runs and +% older matbox-actions versions. It requires Python and the deprecated +% pybadges package, and will be removed in a future major release. arguments label (1,1) string diff --git a/tools/tasks/codecheckToolbox.m b/tools/tasks/codecheckToolbox.m index f781459..c5819b5 100644 --- a/tools/tasks/codecheckToolbox.m +++ b/tools/tasks/codecheckToolbox.m @@ -1,5 +1,5 @@ -function codecheckToolbox() +function codecheckToolbox(varargin) installMatBox() projectRootDir = matboxtools.projectdir(); - matbox.tasks.codecheckToolbox(projectRootDir) + matbox.tasks.codecheckToolbox(projectRootDir, varargin{:}) end diff --git a/tools/tests/+matboxtools/+unittest/TasksTest.m b/tools/tests/+matboxtools/+unittest/TasksTest.m index 40073a1..9ea5c36 100644 --- a/tools/tests/+matboxtools/+unittest/TasksTest.m +++ b/tools/tests/+matboxtools/+unittest/TasksTest.m @@ -34,6 +34,29 @@ function testCodecheckToolbox(testCase) testCase.verifyTrue(isfolder(fullfile(pwd, "docs", "reports"))) end + function testCodecheckToolboxCreatesBadgeJson(testCase) + pathStr = matboxtools.projectdir(); + copyfile(pathStr, pwd); + + % The matbox-actions workflows set this environment variable to + % request badge JSON instead of legacy in-MATLAB SVG rendering. + previousValue = getenv("MATBOX_BADGE_FORMAT"); + setenv("MATBOX_BADGE_FORMAT", "json") + testCase.addTeardown(@() setenv("MATBOX_BADGE_FORMAT", previousValue)) + + matbox.tasks.codecheckToolbox(pwd, ... + "CreateBadge", true, "SaveReport", false); + + badgeFile = fullfile(pwd, "docs", "reports", "badges", "code_issues.json"); + testCase.verifyTrue(isfile(badgeFile), ... + "Expected badge JSON file was not created.") + + badgeInfo = jsondecode(fileread(badgeFile)); + testCase.verifyEqual(badgeInfo.label, 'code issues') + testCase.verifyTrue(ismember(string(badgeInfo.color), ... + ["green", "yellow", "red"])) + end + function testPackageToolbox(testCase) pathStr = matboxtools.projectdir(); copyfile(pathStr, pwd);