org.gradle.internal.locking.LockFileReaderWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.internal.locking;
import com.google.common.collect.ImmutableList;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.internal.DocumentationRegistry;
import org.gradle.api.internal.DomainObjectContext;
import org.gradle.api.internal.file.FileResolver;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.resource.local.FileResourceListener;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class LockFileReaderWriter {
private static final Logger LOGGER = Logging.getLogger(LockFileReaderWriter.class);
private static final DocumentationRegistry DOC_REG = new DocumentationRegistry();
static final String UNIQUE_LOCKFILE_NAME = "gradle.lockfile";
static final String FILE_SUFFIX = ".lockfile";
static final String DEPENDENCY_LOCKING_FOLDER = "gradle/dependency-locks";
static final Charset CHARSET = StandardCharsets.UTF_8;
static final List LOCKFILE_HEADER_LIST = ImmutableList.of("# This is a Gradle generated file for dependency locking.", "# Manual edits can break the build and are not advised.", "# This file is expected to be part of source control.");
static final String EMPTY_CONFIGURATIONS_ENTRY = "empty=";
static final String BUILD_SCRIPT_PREFIX = "buildscript-";
static final String SETTINGS_SCRIPT_PREFIX = "settings-";
private final Path lockFilesRoot;
private final DomainObjectContext context;
private final RegularFileProperty lockFile;
private final FileResourceListener listener;
public LockFileReaderWriter(FileResolver fileResolver, DomainObjectContext context, RegularFileProperty lockFile, FileResourceListener listener) {
this.context = context;
this.lockFile = lockFile;
this.listener = listener;
Path resolve = null;
if (fileResolver.canResolveRelativePath()) {
resolve = fileResolver.resolve(DEPENDENCY_LOCKING_FOLDER).toPath();
// TODO: Can I find a way to use a convention here instead?
lockFile.set(fileResolver.resolve(decorate(UNIQUE_LOCKFILE_NAME)));
}
this.lockFilesRoot = resolve;
LOGGER.debug("Lockfiles root: {}", lockFilesRoot);
}
public void writeLockFile(String configurationName, List resolvedModules) {
checkValidRoot(configurationName);
makeLockfilesRoot();
List content = new ArrayList<>(50);
content.addAll(LOCKFILE_HEADER_LIST);
content.addAll(resolvedModules);
try {
Files.write(lockFilesRoot.resolve(decorate(configurationName) + FILE_SUFFIX), content, CHARSET);
} catch (IOException e) {
throw new RuntimeException("Unable to write lock file", e);
}
}
private void makeLockfilesRoot() {
if (!Files.exists(lockFilesRoot)) {
try {
Files.createDirectories(lockFilesRoot);
} catch (IOException e) {
throw new RuntimeException("Issue creating dependency-lock directory", e);
}
}
}
@Nullable
public List readLockFile(String configurationName) {
checkValidRoot(configurationName);
Path lockFile = lockFilesRoot.resolve(decorate(configurationName) + FILE_SUFFIX);
listener.fileObserved(lockFile.toFile());
if (Files.exists(lockFile)) {
List result;
try {
result = Files.readAllLines(lockFile, CHARSET);
} catch (IOException e) {
throw new RuntimeException("Unable to load lock file", e);
}
List lines = result;
filterNonModuleLines(lines);
return lines;
} else {
return null;
}
}
private String decorate(String configurationName) {
if (context.isScript()) {
if (context.isRootScript()) {
return SETTINGS_SCRIPT_PREFIX + configurationName;
}
return BUILD_SCRIPT_PREFIX + configurationName;
} else {
return configurationName;
}
}
private void checkValidRoot() {
if (lockFilesRoot == null) {
throw new IllegalStateException("Dependency locking cannot be used for project '" + context.getProjectPath() + "'." +
" See limitations in the documentation (" + DOC_REG.getDocumentationFor("dependency_locking", "locking_limitations") +").");
}
}
private void checkValidRoot(String configurationName) {
if (lockFilesRoot == null) {
throw new IllegalStateException("Dependency locking cannot be used for configuration '" + context.identityPath(configurationName) + "'." +
" See limitations in the documentation (" + DOC_REG.getDocumentationFor("dependency_locking", "locking_limitations") +").");
}
}
private void filterNonModuleLines(List lines) {
Iterator iterator = lines.iterator();
while (iterator.hasNext()) {
String value = iterator.next().trim();
if (value.startsWith("#") || value.isEmpty()) {
iterator.remove();
}
}
}
public Map> readUniqueLockFile() {
checkValidRoot();
Predicate empty = String::isEmpty;
Predicate comment = s -> s.startsWith("#");
Path uniqueLockFile = getUniqueLockfilePath();
List emptyConfigurations = new ArrayList<>();
Map> uniqueLockState = new HashMap<>(10);
listener.fileObserved(uniqueLockFile.toFile());
if (Files.exists(uniqueLockFile)) {
try {
Files.lines(uniqueLockFile, CHARSET).filter(empty.or(comment).negate())
.filter(line -> {
if (line.startsWith(EMPTY_CONFIGURATIONS_ENTRY)) {
// This is a perf hack for handling the last line in the file in a special way
collectEmptyConfigurations(line, emptyConfigurations);
return false;
} else {
return true;
}
}).forEach(line -> parseLine(line, uniqueLockState));
} catch (IOException e) {
throw new RuntimeException("Unable to load unique lockfile", e);
}
for (String emptyConfiguration : emptyConfigurations) {
uniqueLockState.computeIfAbsent(emptyConfiguration, k -> new ArrayList<>());
}
return uniqueLockState;
} else {
return new HashMap<>();
}
}
private void collectEmptyConfigurations(String line, List emptyConfigurations) {
if (line.length() > EMPTY_CONFIGURATIONS_ENTRY.length()) {
String[] configurations = line.substring(EMPTY_CONFIGURATIONS_ENTRY.length()).split(",");
Collections.addAll(emptyConfigurations, configurations);
}
}
private Path getUniqueLockfilePath() {
return lockFile.get().getAsFile().toPath();
}
private void parseLine(String line, Map> result) {
String[] split = line.split("=");
String[] configurations = split[1].split(",");
for (String configuration : configurations) {
result.compute(configuration, (k, v) -> {
List mapping;
if (v == null) {
mapping = new ArrayList<>();
} else {
mapping = v;
}
mapping.add(split[0]);
return mapping;
});
}
}
public boolean canWrite() {
return lockFilesRoot != null;
}
public void writeUniqueLockfile(Map> lockState) {
checkValidRoot();
Path lockfilePath = getUniqueLockfilePath();
// Revert mapping
Map> dependencyToConfigurations = new TreeMap<>();
List emptyConfigurations = new ArrayList<>();
mapLockStateFromDependencyToConfiguration(lockState, dependencyToConfigurations, emptyConfigurations);
writeUniqueLockfile(lockfilePath, dependencyToConfigurations, emptyConfigurations);
}
private void writeUniqueLockfile(Path lockfilePath, Map> dependencyToConfigurations, List emptyConfigurations) {
try {
Files.createDirectories(lockfilePath.getParent());
List content = new ArrayList<>(50);
content.addAll(LOCKFILE_HEADER_LIST);
for (Map.Entry> entry : dependencyToConfigurations.entrySet()) {
String builder = entry.getKey() + "=" + entry.getValue().stream().sorted().collect(Collectors.joining(","));
content.add(builder);
}
content.add("empty=" + emptyConfigurations.stream().sorted().collect(Collectors.joining(",")));
Files.write(lockfilePath, content, CHARSET);
} catch (IOException e) {
throw new RuntimeException("Unable to write unique lockfile", e);
}
}
private void mapLockStateFromDependencyToConfiguration(Map> lockState, Map> dependencyToConfigurations, List emptyConfigurations) {
for (Map.Entry> entry : lockState.entrySet()) {
List dependencies = entry.getValue();
if (dependencies.isEmpty()) {
emptyConfigurations.add(entry.getKey());
} else {
for (String dependency : dependencies) {
dependencyToConfigurations.compute(dependency, (k, v) -> {
List confs = v;
if (v == null) {
confs = new ArrayList<>();
}
confs.add(entry.getKey());
return confs;
});
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy