From 8df2fa751ca6f29c1444c8ddb882c39d5e5e272d Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 15 Oct 2026 19:49:05 +0200 Subject: [PATCH 1/4] fix: install add-ons without agreeToLicense on R2026b and later From R2026b, matlab.addons.install accepts the license agreement by default and errors when the agreeToLicense argument is false (matlab_addons:install:licenseAgreementDialogDeprecationNotice). An argument of true is still accepted. installFexPackage passed the AgreeToLicense option, which defaults to false, so installing a FileExchange requirement packaged as .mltbx failed on R2026b. It now calls matlab.addons.install without the argument from R2026b. Earlier releases still receive AgreeToLicense. Co-Authored-By: Claude Opus 5.5 --- code/+matbox/+setup/+internal/installFexPackage.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/+matbox/+setup/+internal/installFexPackage.m b/code/+matbox/+setup/+internal/installFexPackage.m index cf16696..4c4b568 100644 --- a/code/+matbox/+setup/+internal/installFexPackage.m +++ b/code/+matbox/+setup/+internal/installFexPackage.m @@ -101,7 +101,13 @@ elseif endsWith(addonUrl, '/mltbx') [tempFilepath, C] = matbox.setup.internal.utility.tempsave(addonUrl, [toolboxIdentifier, '_temp.mltbx']); - installedAddon = matlab.addons.install(tempFilepath, options.AgreeToLicense); + if isMATLABReleaseOlderThan("R2026b") + installedAddon = matlab.addons.install(tempFilepath, options.AgreeToLicense); + else + % From R2026b, installing accepts the license agreement, + % and an agreeToLicense argument of false is an error. + installedAddon = matlab.addons.install(tempFilepath); + end if isempty(installedAddon) fprintf(newline) error('Failed to install "%s"...', toolboxName) From 29863729e9f93394def46deec685936e883b2cf0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:01:15 +0000 Subject: [PATCH 2/4] Update GitHub badges [skip ci] --- .github/badges/code_issues.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg index 6071791..da10096 100644 --- a/.github/badges/code_issues.svg +++ b/.github/badges/code_issues.svg @@ -1 +1 @@ -code issues: 1code issues1 +code issues: 2code issues2 From 35e46631c4097c2b844763e135f2c1f0e86a908b Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 15 Oct 2026 20:06:51 +0200 Subject: [PATCH 3/4] feat: warn when an explicit AgreeToLicense false is ignored on R2026b From R2026b MATLAB accepts an add-on's license agreement on install, so AgreeToLicense false can no longer decline it. installFexPackage now warns (MatBox:Setup:AgreeToLicenseIgnored) after installing an .mltbx package on R2026b or later when the caller passed false. The option has no default in installRequirements, installFromSourceUri and installFexPackage, and is forwarded only when given, so omitting it does not warn. On earlier releases an omitted option still means false. SetupTest checks on R2026b and later that an explicit false warns and that an omitted option does not. Both uninstall the test add-on first, because installFexPackage skips an add-on that is already installed. Co-Authored-By: Claude Opus 5.5 --- .../+setup/+internal/installFexPackage.m | 19 +++++++-- code/+matbox/+setup/installFromSourceUri.m | 11 ++++- code/+matbox/installRequirements.m | 11 ++++- .../tests/+matboxtools/+unittest/SetupTest.m | 40 +++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/code/+matbox/+setup/+internal/installFexPackage.m b/code/+matbox/+setup/+internal/installFexPackage.m index 4c4b568..eef2f47 100644 --- a/code/+matbox/+setup/+internal/installFexPackage.m +++ b/code/+matbox/+setup/+internal/installFexPackage.m @@ -24,9 +24,14 @@ options.AddToPath (1,1) logical = true options.AddToPathWithSubfolders (1,1) logical = true options.Verbose (1,1) logical = true - options.AgreeToLicense (1,1) logical = false + % No default, so an explicit false can be told from an omitted value + options.AgreeToLicense (1,1) logical end + isLicenseOptionGiven = isfield(options, "AgreeToLicense"); + isLicenseAgreed = isLicenseOptionGiven && options.AgreeToLicense; + isLicenseDeclined = isLicenseOptionGiven && ~options.AgreeToLicense; + % Check if toolbox is installed [isInstalled, version, toolboxFolder] = matbox.setup.internal.fex.isToolboxInstalled(toolboxIdentifier, options.Version); @@ -101,8 +106,9 @@ elseif endsWith(addonUrl, '/mltbx') [tempFilepath, C] = matbox.setup.internal.utility.tempsave(addonUrl, [toolboxIdentifier, '_temp.mltbx']); - if isMATLABReleaseOlderThan("R2026b") - installedAddon = matlab.addons.install(tempFilepath, options.AgreeToLicense); + isLicenseOptionSupported = isMATLABReleaseOlderThan("R2026b"); + if isLicenseOptionSupported + installedAddon = matlab.addons.install(tempFilepath, isLicenseAgreed); else % From R2026b, installing accepts the license agreement, % and an agreeToLicense argument of false is an error. @@ -112,6 +118,13 @@ fprintf(newline) error('Failed to install "%s"...', toolboxName) end + if isLicenseDeclined && ~isLicenseOptionSupported + fprintf(newline) + warning("MatBox:Setup:AgreeToLicenseIgnored", ... + ['AgreeToLicense is false, but from R2026b MATLAB accepts the ', ... + 'license agreement when it installs an add-on. "%s" was installed ', ... + 'and its license terms apply.'], toolboxName) + end packageTargetFolder = string(missing); installationMethod = "mltbx"; end diff --git a/code/+matbox/+setup/installFromSourceUri.m b/code/+matbox/+setup/installFromSourceUri.m index 5e85760..807cdb5 100644 --- a/code/+matbox/+setup/installFromSourceUri.m +++ b/code/+matbox/+setup/installFromSourceUri.m @@ -23,10 +23,17 @@ options.AddToPath (1,1) logical = true options.Update (1,1) logical = false options.Verbose (1,1) logical = true - options.AgreeToLicense (1,1) logical = false + options.AgreeToLicense (1,1) logical end if startsWith(sourceUri, "fex://") + % AgreeToLicense has no default, so the installer can tell an + % explicit choice from an omitted one. Forward it only when given. + licenseArgs = {}; + if isfield(options, "AgreeToLicense") + licenseArgs = {"AgreeToLicense", options.AgreeToLicense}; + end + [packageUuid, title, version] = getFEXPackageSpecification(sourceUri); [packageTargetFolder, installationType] = matbox.setup.internal.installFexPackage( ... packageUuid, ... @@ -35,7 +42,7 @@ "Version", version, ... "AddToPath", options.AddToPath, ... "Verbose", options.Verbose, ... - "AgreeToLicense", options.AgreeToLicense); + licenseArgs{:}); installResult = struct( ... "FilePath", packageTargetFolder, ... diff --git a/code/+matbox/installRequirements.m b/code/+matbox/installRequirements.m index a225ef9..81fc0f2 100644 --- a/code/+matbox/installRequirements.m +++ b/code/+matbox/installRequirements.m @@ -12,13 +12,20 @@ function installRequirements(toolboxFolder, mode, options) options.SaveSearchPath (1,1) logical = true options.InstallationLocation (1,1) string = matbox.setup.internal.getDefaultAddonFolder() options.Verbose (1,1) logical = true - options.AgreeToLicenses (1,1) logical = false + options.AgreeToLicenses (1,1) logical end % Parse mode/flags mode = string(mode); doUpdate = any(strcmp(mode, 'update')) || any(strcmp(mode, 'u')); + % AgreeToLicenses has no default, so the installer can tell an explicit + % choice from an omitted one. Forward it only when given. + licenseArgs = {}; + if isfield(options, "AgreeToLicenses") + licenseArgs = {"AgreeToLicense", options.AgreeToLicenses}; + end + installationLocation = options.InstallationLocation; if ~isfolder(installationLocation); mkdir(installationLocation); end @@ -32,7 +39,7 @@ function installRequirements(toolboxFolder, mode, options) "AddToPath", options.UpdateSearchPath, ... "Update", doUpdate, ... "Verbose", options.Verbose, ... - "AgreeToLicense", options.AgreeToLicenses); + licenseArgs{:}); case 'Unknown' continue end diff --git a/tools/tests/+matboxtools/+unittest/SetupTest.m b/tools/tests/+matboxtools/+unittest/SetupTest.m index 36a5e97..aca8cbe 100644 --- a/tools/tests/+matboxtools/+unittest/SetupTest.m +++ b/tools/tests/+matboxtools/+unittest/SetupTest.m @@ -1,6 +1,11 @@ classdef SetupTest < matlab.unittest.TestCase % BasicTest - Unit test for testing the openMINDS tutorials. + properties (Constant) + % A FileExchange package that is distributed as an .mltbx file + FexMltbxUri = "fex://160058-recursively-list-files-and-folders" + end + methods (TestClassSetup) function setupClass(testCase) %#ok<*MANU> % pass @@ -35,9 +40,44 @@ function testUpdateGitRequirementWithGit(testCase) testCase.installRequirements("Update", true) rmpath(genpath(fullfile(pwd, "StructEditor"))) end + + function testExplicitAgreeToLicenseFalseWarns(testCase) + testCase.assumeFalse(isMATLABReleaseOlderThan("R2026b"), ... + "MATLAB ignores a declined add-on license only from R2026b.") + testCase.uninstallFexAddon(testCase.FexMltbxUri) + + testCase.verifyWarning( ... + @() matbox.setup.installFromSourceUri(testCase.FexMltbxUri, ... + "InstallationLocation", pwd, "AgreeToLicense", false), ... + "MatBox:Setup:AgreeToLicenseIgnored") + end + + function testOmittedAgreeToLicenseDoesNotWarn(testCase) + testCase.assumeFalse(isMATLABReleaseOlderThan("R2026b"), ... + "MATLAB ignores a declined add-on license only from R2026b.") + testCase.uninstallFexAddon(testCase.FexMltbxUri) + + % No warning follows the license warning in the install path, + % so the last warning shows whether it was issued. + lastwarn("", "") + matbox.setup.installFromSourceUri(testCase.FexMltbxUri, "InstallationLocation", pwd) + [~, lastWarningId] = lastwarn(); + testCase.verifyNotEqual(lastWarningId, 'MatBox:Setup:AgreeToLicenseIgnored') + end end methods + function uninstallFexAddon(~, sourceUri) + % installFexPackage skips an add-on that is already installed, so + % remove every installed version to make the next call install it. + addonIdentifier = matbox.setup.internal.fex.parseFileExchangeURI(sourceUri); + addons = matlab.addons.installedAddons(); + addonVersions = addons.Version(addons.Identifier == addonIdentifier); + for addonVersion = reshape(addonVersions, 1, []) + matlab.addons.uninstall(addonIdentifier, addonVersion) + end + end + function installRequirements(testCase, options) arguments From f9e507f03e0cc71a27f55d36058de54221dff91d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:09:24 +0000 Subject: [PATCH 4/4] Update GitHub badges [skip ci] --- .github/badges/tests.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index b740c54..7afe4fa 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -tests: 34 passedtests34 passed +tests: 36 passedtests36 passed