[MNG-5659] Provide project-specific settings located in ${session.rootdir}/.mvn/settings.xml by default (#1059)

This commit is contained in:
Guillaume Nodet 2023-06-19 12:32:14 +02:00 committed by GitHub
parent edeff8643e
commit e6303aae32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 346 additions and 14 deletions

View File

@ -48,7 +48,7 @@ public interface SettingsBuilder extends Service {
@Nonnull
default SettingsBuilderResult build(
@Nonnull Session session, @Nonnull Source globalSettingsSource, @Nonnull Source userSettingsSource) {
return build(SettingsBuilderRequest.build(session, globalSettingsSource, userSettingsSource));
return build(session, globalSettingsSource, null, userSettingsSource);
}
/**
@ -60,6 +60,37 @@ public interface SettingsBuilder extends Service {
@Nonnull
default SettingsBuilderResult build(
@Nonnull Session session, @Nonnull Path globalSettingsPath, @Nonnull Path userSettingsPath) {
return build(SettingsBuilderRequest.build(session, globalSettingsPath, userSettingsPath));
return build(session, globalSettingsPath, null, userSettingsPath);
}
/**
* Builds the effective settings of the specified settings sources.
*
* @return the result of the settings building, never {@code null}
* @throws SettingsBuilderException if the effective settings could not be built
*/
@Nonnull
default SettingsBuilderResult build(
@Nonnull Session session,
@Nonnull Source globalSettingsSource,
@Nonnull Source projectSettingsSource,
@Nonnull Source userSettingsSource) {
return build(
SettingsBuilderRequest.build(session, globalSettingsSource, projectSettingsSource, userSettingsSource));
}
/**
* Builds the effective settings of the specified settings paths.
*
* @return the result of the settings building, never {@code null}
* @throws SettingsBuilderException if the effective settings could not be built
*/
@Nonnull
default SettingsBuilderResult build(
@Nonnull Session session,
@Nonnull Path globalSettingsPath,
@Nonnull Path projectSettingsPath,
@Nonnull Path userSettingsPath) {
return build(SettingsBuilderRequest.build(session, globalSettingsPath, projectSettingsPath, userSettingsPath));
}
}

View File

@ -56,6 +56,22 @@ public interface SettingsBuilderRequest {
@Nonnull
Optional<Source> getGlobalSettingsSource();
/**
* Gets the project settings source.
*
* @return the project settings source or {@code null} if none
*/
@Nonnull
Optional<Source> getProjectSettingsSource();
/**
* Gets the project settings path.
*
* @return the project settings path or {@code null} if none
*/
@Nonnull
Optional<Path> getProjectSettingsPath();
/**
* Gets the user settings path.
*
@ -75,19 +91,39 @@ public interface SettingsBuilderRequest {
@Nonnull
static SettingsBuilderRequest build(
@Nonnull Session session, @Nonnull Source globalSettingsSource, @Nonnull Source userSettingsSource) {
return build(session, globalSettingsSource, null, userSettingsSource);
}
@Nonnull
static SettingsBuilderRequest build(
@Nonnull Session session, @Nonnull Path globalSettingsPath, @Nonnull Path userSettingsPath) {
return build(session, globalSettingsPath, null, userSettingsPath);
}
@Nonnull
static SettingsBuilderRequest build(
@Nonnull Session session,
@Nonnull Source globalSettingsSource,
@Nonnull Source projectSettingsSource,
@Nonnull Source userSettingsSource) {
return builder()
.session(nonNull(session, "session cannot be null"))
.globalSettingsSource(nonNull(globalSettingsSource, "globalSettingsSource cannot be null"))
.projectSettingsSource(nonNull(projectSettingsSource, "projectSettingsSource cannot be null"))
.userSettingsSource(nonNull(userSettingsSource, "userSettingsSource cannot be null"))
.build();
}
@Nonnull
static SettingsBuilderRequest build(
@Nonnull Session session, @Nonnull Path globalSettingsPath, @Nonnull Path userSettingsPath) {
@Nonnull Session session,
@Nonnull Path globalSettingsPath,
@Nonnull Path projectSettingsPath,
@Nonnull Path userSettingsPath) {
return builder()
.session(nonNull(session, "session cannot be null"))
.globalSettingsPath(nonNull(globalSettingsPath, "globalSettingsPath cannot be null"))
.projectSettingsPath(nonNull(projectSettingsPath, "projectSettingsPath cannot be null"))
.userSettingsPath(nonNull(userSettingsPath, "userSettingsPath cannot be null"))
.build();
}
@ -102,6 +138,8 @@ public interface SettingsBuilderRequest {
Session session;
Path globalSettingsPath;
Source globalSettingsSource;
Path projectSettingsPath;
Source projectSettingsSource;
Path userSettingsPath;
Source userSettingsSource;
@ -120,6 +158,16 @@ public interface SettingsBuilderRequest {
return this;
}
public SettingsBuilderRequestBuilder projectSettingsPath(Path projectSettingsPath) {
this.projectSettingsPath = projectSettingsPath;
return this;
}
public SettingsBuilderRequestBuilder projectSettingsSource(Source projectSettingsSource) {
this.projectSettingsSource = projectSettingsSource;
return this;
}
public SettingsBuilderRequestBuilder userSettingsPath(Path userSettingsPath) {
this.userSettingsPath = userSettingsPath;
return this;
@ -132,12 +180,20 @@ public interface SettingsBuilderRequest {
public SettingsBuilderRequest build() {
return new DefaultSettingsBuilderRequest(
session, globalSettingsPath, globalSettingsSource, userSettingsPath, userSettingsSource);
session,
globalSettingsPath,
globalSettingsSource,
projectSettingsPath,
projectSettingsSource,
userSettingsPath,
userSettingsSource);
}
private static class DefaultSettingsBuilderRequest extends BaseRequest implements SettingsBuilderRequest {
private final Path globalSettingsPath;
private final Source globalSettingsSource;
private final Path projectSettingsPath;
private final Source projectSettingsSource;
private final Path userSettingsPath;
private final Source userSettingsSource;
@ -146,11 +202,15 @@ public interface SettingsBuilderRequest {
@Nonnull Session session,
@Nullable Path globalSettingsPath,
@Nullable Source globalSettingsSource,
@Nullable Path projectSettingsPath,
@Nullable Source projectSettingsSource,
@Nullable Path userSettingsPath,
@Nullable Source userSettingsSource) {
super(session);
this.globalSettingsPath = globalSettingsPath;
this.globalSettingsSource = globalSettingsSource;
this.projectSettingsPath = projectSettingsPath;
this.projectSettingsSource = projectSettingsSource;
this.userSettingsPath = userSettingsPath;
this.userSettingsSource = userSettingsSource;
}
@ -167,6 +227,18 @@ public interface SettingsBuilderRequest {
return Optional.ofNullable(globalSettingsSource);
}
@Nonnull
@Override
public Optional<Path> getProjectSettingsPath() {
return Optional.ofNullable(projectSettingsPath);
}
@Nonnull
@Override
public Optional<Source> getProjectSettingsSource() {
return Optional.ofNullable(projectSettingsSource);
}
@Nonnull
@Override
public Optional<Path> getUserSettingsPath() {

View File

@ -53,6 +53,7 @@
<code>
<![CDATA[
public static final String USER_LEVEL = "user-level";
public static final String PROJECT_LEVEL = "project-level";
public static final String GLOBAL_LEVEL = "global-level";
private String sourceLevel = USER_LEVEL;
@ -64,9 +65,9 @@
{
throw new IllegalStateException( "Cannot reset sourceLevel attribute; it is already set to: " + sourceLevel );
}
else if ( !( USER_LEVEL.equals( sourceLevel ) || GLOBAL_LEVEL.equals( sourceLevel ) ) )
else if ( !( USER_LEVEL.equals( sourceLevel ) || PROJECT_LEVEL.equals( sourceLevel ) || GLOBAL_LEVEL.equals( sourceLevel ) ) )
{
throw new IllegalArgumentException( "sourceLevel must be one of: {" + USER_LEVEL + "," + GLOBAL_LEVEL + "}" );
throw new IllegalArgumentException( "sourceLevel must be one of: {" + USER_LEVEL + "," + PROJECT_LEVEL + "," + GLOBAL_LEVEL + "}" );
}
else
{

View File

@ -87,6 +87,7 @@ public class DefaultMavenSettingsBuilder extends AbstractLogEnabled implements M
public Settings buildSettings(MavenExecutionRequest request) throws IOException, XmlPullParserException {
SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();
settingsRequest.setUserSettingsFile(request.getUserSettingsFile());
settingsRequest.setProjectSettingsFile(request.getProjectSettingsFile());
settingsRequest.setGlobalSettingsFile(request.getGlobalSettingsFile());
settingsRequest.setUserProperties(request.getUserProperties());
settingsRequest.setSystemProperties(request.getSystemProperties());

View File

@ -90,6 +90,8 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
private File userSettingsFile;
private File projectSettingsFile;
private File globalSettingsFile;
private File userToolchainsFile;
@ -842,6 +844,18 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
return this;
}
@Override
public File getProjectSettingsFile() {
return projectSettingsFile;
}
@Override
public MavenExecutionRequest setProjectSettingsFile(File projectSettingsFile) {
this.projectSettingsFile = projectSettingsFile;
return this;
}
@Override
public File getGlobalSettingsFile() {
return globalSettingsFile;

View File

@ -393,6 +393,10 @@ public interface MavenExecutionRequest {
MavenExecutionRequest setUserSettingsFile(File userSettingsFile);
File getProjectSettingsFile();
MavenExecutionRequest setProjectSettingsFile(File projectSettingsFile);
File getGlobalSettingsFile();
MavenExecutionRequest setGlobalSettingsFile(File globalSettingsFile);

View File

@ -78,6 +78,8 @@ public class CLIManager {
public static final char ALTERNATE_USER_SETTINGS = 's';
public static final String ALTERNATE_PROJECT_SETTINGS = "ps";
public static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
public static final char ALTERNATE_USER_TOOLCHAINS = 't';
@ -205,6 +207,11 @@ public class CLIManager {
.desc("Alternate path for the user settings file")
.hasArg()
.build());
options.addOption(Option.builder(ALTERNATE_PROJECT_SETTINGS)
.longOpt("project-settings")
.desc("Alternate path for the project settings file")
.hasArg()
.build());
options.addOption(Option.builder(ALTERNATE_GLOBAL_SETTINGS)
.longOpt("global-settings")
.desc("Alternate path for the global settings file")

View File

@ -110,4 +110,12 @@ public class CliRequest {
public void setUserProperties(Properties properties) {
this.userProperties.putAll(properties);
}
public Path getRootDirectory() {
return rootDirectory;
}
public Path getTopDirectory() {
return topDirectory;
}
}

View File

@ -25,6 +25,7 @@ import javax.inject.Singleton;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
import org.apache.maven.artifact.InvalidRepositoryException;
@ -66,6 +67,8 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
public static final File DEFAULT_USER_SETTINGS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "settings.xml");
public static final File DEFAULT_PROJECT_SETTINGS_FILE = new File(".mvn", "settings.xml");
public static final File DEFAULT_GLOBAL_SETTINGS_FILE = new File(System.getProperty("maven.conf"), "settings.xml");
private static final Logger LOGGER = LoggerFactory.getLogger(SettingsXmlConfigurationProcessor.class);
@ -98,6 +101,24 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
userSettingsFile = DEFAULT_USER_SETTINGS_FILE;
}
File projectSettingsFile;
if (commandLine.hasOption(CLIManager.ALTERNATE_PROJECT_SETTINGS)) {
projectSettingsFile = new File(commandLine.getOptionValue(CLIManager.ALTERNATE_PROJECT_SETTINGS));
projectSettingsFile = resolveFile(projectSettingsFile, workingDirectory);
if (!projectSettingsFile.isFile()) {
throw new FileNotFoundException(
"The specified project settings file does not exist: " + projectSettingsFile);
}
} else if (cliRequest.getRootDirectory() != null) {
projectSettingsFile = DEFAULT_PROJECT_SETTINGS_FILE;
projectSettingsFile = resolveFile(
projectSettingsFile, cliRequest.getRootDirectory().toString());
} else {
projectSettingsFile = null;
}
File globalSettingsFile;
if (commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_SETTINGS)) {
@ -113,13 +134,21 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
}
request.setGlobalSettingsFile(globalSettingsFile);
request.setProjectSettingsFile(projectSettingsFile);
request.setUserSettingsFile(userSettingsFile);
SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();
settingsRequest.setGlobalSettingsFile(globalSettingsFile);
settingsRequest.setProjectSettingsFile(projectSettingsFile);
settingsRequest.setUserSettingsFile(userSettingsFile);
settingsRequest.setSystemProperties(cliRequest.getSystemProperties());
settingsRequest.setUserProperties(cliRequest.getUserProperties());
Properties props = cliRequest.getUserProperties();
if (cliRequest.getRootDirectory() != null) {
props = new Properties();
props.putAll(cliRequest.getUserProperties());
props.put("session.rootDirectory", cliRequest.getRootDirectory().toString());
}
settingsRequest.setUserProperties(props);
if (request.getEventSpyDispatcher() != null) {
request.getEventSpyDispatcher().onEvent(settingsRequest);
@ -128,6 +157,9 @@ public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor
LOGGER.debug(
"Reading global settings from '{}'",
getLocation(settingsRequest.getGlobalSettingsSource(), settingsRequest.getGlobalSettingsFile()));
LOGGER.debug(
"Reading project settings from '{}'",
getLocation(settingsRequest.getProjectSettingsSource(), settingsRequest.getProjectSettingsFile()));
LOGGER.debug(
"Reading user settings from '{}'",
getLocation(settingsRequest.getUserSettingsSource(), settingsRequest.getUserSettingsFile()));

View File

@ -78,7 +78,13 @@ under the License.
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration />
<configuration>
<parameter>
<excludes>
<exclude>org.apache.maven.settings.validation.SettingsValidator#validate(org.apache.maven.settings.Settings,boolean,org.apache.maven.settings.building.SettingsProblemCollector):METHOD_NEW_DEFAULT</exclude>
</excludes>
</parameter>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -32,6 +32,7 @@ import java.util.Map;
import org.apache.maven.building.FileSource;
import org.apache.maven.building.Source;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.TrackableBase;
import org.apache.maven.settings.io.SettingsParseException;
@ -90,12 +91,17 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
Source globalSettingsSource =
getSettingsSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource());
Settings globalSettings = readSettings(globalSettingsSource, request, problems);
Settings globalSettings = readSettings(globalSettingsSource, false, request, problems);
Source projectSettingsSource =
getSettingsSource(request.getProjectSettingsFile(), request.getProjectSettingsSource());
Settings projectSettings = readSettings(projectSettingsSource, true, request, problems);
Source userSettingsSource = getSettingsSource(request.getUserSettingsFile(), request.getUserSettingsSource());
Settings userSettings = readSettings(userSettingsSource, request, problems);
Settings userSettings = readSettings(userSettingsSource, false, request, problems);
settingsMerger.merge(userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL);
settingsMerger.merge(projectSettings, globalSettings, TrackableBase.GLOBAL_LEVEL);
settingsMerger.merge(userSettings, projectSettings, TrackableBase.PROJECT_LEVEL);
problems.setSource("");
@ -139,7 +145,10 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
}
private Settings readSettings(
Source settingsSource, SettingsBuildingRequest request, DefaultSettingsProblemCollector problems) {
Source settingsSource,
boolean isProjectSettings,
SettingsBuildingRequest request,
DefaultSettingsProblemCollector problems) {
if (settingsSource == null) {
return new Settings();
}
@ -179,7 +188,23 @@ public class DefaultSettingsBuilder implements SettingsBuilder {
return new Settings();
}
settingsValidator.validate(settings, problems);
settingsValidator.validate(settings, isProjectSettings, problems);
if (isProjectSettings) {
settings.setLocalRepository(null);
settings.setInteractiveMode(true);
settings.setOffline(false);
settings.setProxies(Collections.emptyList());
settings.setUsePluginRegistry(false);
for (Server server : settings.getServers()) {
server.setUsername(null);
server.setPassword(null);
server.setPrivateKey(null);
server.setPassword(null);
server.setFilePermissions(null);
server.setDirectoryPermissions(null);
}
}
return settings;
}

View File

@ -30,10 +30,14 @@ public class DefaultSettingsBuildingRequest implements SettingsBuildingRequest {
private File globalSettingsFile;
private File projectSettingsFile;
private File userSettingsFile;
private SettingsSource globalSettingsSource;
private SettingsSource projectSettingsSource;
private SettingsSource userSettingsSource;
private Properties systemProperties;
@ -64,6 +68,30 @@ public class DefaultSettingsBuildingRequest implements SettingsBuildingRequest {
return this;
}
@Override
public File getProjectSettingsFile() {
return projectSettingsFile;
}
@Override
public DefaultSettingsBuildingRequest setProjectSettingsFile(File projectSettingsFile) {
this.projectSettingsFile = projectSettingsFile;
return this;
}
@Override
public SettingsSource getProjectSettingsSource() {
return projectSettingsSource;
}
@Override
public DefaultSettingsBuildingRequest setProjectSettingsSource(SettingsSource projectSettingsSource) {
this.projectSettingsSource = projectSettingsSource;
return this;
}
@Override
public File getUserSettingsFile() {
return userSettingsFile;

View File

@ -19,7 +19,8 @@
package org.apache.maven.settings.building;
/**
* Builds the effective settings from a user settings file and/or a global settings file.
* Builds the effective settings from a user settings file, a project settings file
* and/or a global settings file.
*
* @author Benjamin Bentmann
*/

View File

@ -60,6 +60,40 @@ public interface SettingsBuildingRequest {
*/
SettingsBuildingRequest setGlobalSettingsSource(SettingsSource globalSettingsSource);
/**
* Gets the project settings file.
*
* @return The project settings file or {@code null} if none.
* @since 4.0.0
*/
File getProjectSettingsFile();
/**
* Sets the project settings file. A non-existent settings file is equivalent to empty settings.
*
* @param projectSettingsFile The project settings file, may be {@code null} to disable project settings.
* @return This request, never {@code null}.
* @since 4.0.0
*/
DefaultSettingsBuildingRequest setProjectSettingsFile(File projectSettingsFile);
/**
* Gets the project settings source.
*
* @return The project settings source or {@code null} if none.
* @since 4.0.0
*/
SettingsSource getProjectSettingsSource();
/**
* Sets the project settings source.
*
* @param projectSettingsSource The project settings source, may be {@code null} to disable global settings.
* @return This request, never {@code null}.
* @since 4.0.0
*/
SettingsBuildingRequest setProjectSettingsSource(SettingsSource projectSettingsSource);
/**
* Gets the user settings file.
*

View File

@ -49,6 +49,44 @@ public class DefaultSettingsValidator implements SettingsValidator {
@Override
public void validate(Settings settings, SettingsProblemCollector problems) {
validate(settings, false, problems);
}
@Override
public void validate(Settings settings, boolean isProjectSettings, SettingsProblemCollector problems) {
if (isProjectSettings) {
String msgS = "is not supported on project settings.";
String msgP = "are not supported on project settings.";
if (settings.getLocalRepository() != null
&& !settings.getLocalRepository().isEmpty()) {
addViolation(problems, Severity.WARNING, "localRepository", null, msgS);
}
if (settings.getInteractiveMode() != null && !settings.getInteractiveMode()) {
addViolation(problems, Severity.WARNING, "interactiveMode", null, msgS);
}
if (settings.isOffline()) {
addViolation(problems, Severity.WARNING, "offline", null, msgS);
}
if (!settings.getProxies().isEmpty()) {
addViolation(problems, Severity.WARNING, "proxies", null, msgP);
}
if (settings.isUsePluginRegistry()) {
addViolation(problems, Severity.WARNING, "usePluginRegistry", null, msgS);
}
List<Server> servers = settings.getServers();
for (int i = 0; i < servers.size(); i++) {
Server server = servers.get(i);
String serverField = "servers.server[" + i + "]";
validateStringEmpty(problems, serverField + ".username", server.getUsername(), msgS);
validateStringEmpty(problems, serverField + ".password", server.getPassword(), msgS);
validateStringEmpty(problems, serverField + ".privateKey", server.getPrivateKey(), msgS);
validateStringEmpty(problems, serverField + ".passphrase", server.getPassphrase(), msgS);
validateStringEmpty(problems, serverField + ".filePermissions", server.getFilePermissions(), msgS);
validateStringEmpty(
problems, serverField + ".directoryPermissions", server.getDirectoryPermissions(), msgS);
}
}
if (settings.isUsePluginRegistry()) {
addViolation(problems, Severity.WARNING, "usePluginRegistry", null, "is deprecated and has no effect.");
}
@ -207,6 +245,25 @@ public class DefaultSettingsValidator implements SettingsValidator {
// Field validation
// ----------------------------------------------------------------------
/**
* Asserts:
* <p/>
* <ul>
* <li><code>string.length == null</code>
* <li><code>string.length == 0</code>
* </ul>
*/
private static boolean validateStringEmpty(
SettingsProblemCollector problems, String fieldName, String string, String message) {
if (string == null || string.length() == 0) {
return true;
}
addViolation(problems, Severity.WARNING, fieldName, null, message);
return false;
}
/**
* Asserts:
* <p/>

View File

@ -35,4 +35,15 @@ public interface SettingsValidator {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
void validate(Settings settings, SettingsProblemCollector problems);
/**
* Validate the specified settings.
*
* @param settings The settings to validate, must not be {@code null}.
* @param isProjectSettings Boolean indicating if the validation is for project settings or user / global settings.
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
default void validate(Settings settings, boolean isProjectSettings, SettingsProblemCollector problems) {
validate(settings, problems);
}
}