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 @@
-
+
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 @@
-
+
diff --git a/code/+matbox/+setup/+internal/installFexPackage.m b/code/+matbox/+setup/+internal/installFexPackage.m
index cf16696..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,11 +106,25 @@
elseif endsWith(addonUrl, '/mltbx')
[tempFilepath, C] = matbox.setup.internal.utility.tempsave(addonUrl, [toolboxIdentifier, '_temp.mltbx']);
- 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.
+ installedAddon = matlab.addons.install(tempFilepath);
+ end
if isempty(installedAddon)
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