Skip to content

Commit

Permalink
Fix creation of large build job archives (#102023)
Browse files Browse the repository at this point in the history
* Fix large file and large path handling in build tar creating
  • Loading branch information
breskeby authored and gmarouli committed Nov 22, 2023
1 parent 5a79608 commit d6332a9
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 94 deletions.
7 changes: 7 additions & 0 deletions build-tools-internal/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ gradlePlugin {
id = 'elasticsearch.build'
implementationClass = 'org.elasticsearch.gradle.internal.BuildPlugin'
}
buildComplete {
id = 'elasticsearch.build-complete'
implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchBuildCompletePlugin'
}
distro {
id = 'elasticsearch.distro'
implementationClass = 'org.elasticsearch.gradle.internal.distribution.ElasticsearchDistributionPlugin'
Expand Down Expand Up @@ -266,6 +270,8 @@ dependencies {
api buildLibs.apache.rat
api buildLibs.jna
api buildLibs.shadow.plugin
api buildLibs.gradle.enterprise

// for our ide tweaking
api buildLibs.idea.ext
// When upgrading forbidden apis, ensure dependency version is bumped in ThirdPartyPrecommitPlugin as well
Expand All @@ -280,6 +286,7 @@ dependencies {
api buildLibs.asm.tree
api buildLibs.httpclient
api buildLibs.httpcore

compileOnly buildLibs.checkstyle
runtimeOnly "org.elasticsearch.gradle:reaper:$version"
testImplementation buildLibs.checkstyle
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ buildScan {
}

buildFinished { result ->

buildScanPublished { scan ->
// Attach build scan link as build metadata
// See: https://buildkite.com/docs/pipelines/build-meta-data
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.gradle.internal;

import com.gradle.scan.plugin.BuildScanExtension;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.apache.commons.io.IOUtils;
import org.elasticsearch.gradle.util.GradleUtils;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.flow.FlowAction;
import org.gradle.api.flow.FlowParameters;
import org.gradle.api.flow.FlowProviders;
import org.gradle.api.flow.FlowScope;
import org.gradle.api.internal.file.FileOperations;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

public abstract class ElasticsearchBuildCompletePlugin implements Plugin<Project> {

@Inject
protected abstract FlowScope getFlowScope();

@Inject
protected abstract FlowProviders getFlowProviders();

@Inject
protected abstract FileOperations getFileOperations();

@Override
public void apply(Project target) {
String buildNumber = System.getenv("BUILD_NUMBER") != null
? System.getenv("BUILD_NUMBER")
: System.getenv("BUILDKITE_BUILD_NUMBER");
String performanceTest = System.getenv("BUILD_PERFORMANCE_TEST");
if (buildNumber != null && performanceTest == null && GradleUtils.isIncludedBuild(target) == false) {
File targetFile = target.file("build/" + buildNumber + ".tar.bz2");
File projectDir = target.getProjectDir();
File gradleWorkersDir = new File(target.getGradle().getGradleUserHomeDir(), "workers/");
BuildScanExtension extension = target.getExtensions().getByType(BuildScanExtension.class);
File daemonsLogDir = new File(target.getGradle().getGradleUserHomeDir(), "daemon/" + target.getGradle().getGradleVersion());

getFlowScope().always(BuildFinishedFlowAction.class, spec -> {
spec.getParameters().getBuildScan().set(extension);
spec.getParameters().getUploadFile().set(targetFile);
spec.getParameters().getProjectDir().set(projectDir);
spec.getParameters().getFilteredFiles().addAll(getFlowProviders().getBuildWorkResult().map((result) -> {
System.out.println("Build Finished Action: Collecting archive files...");
List<File> files = new ArrayList<>();
files.addAll(resolveProjectLogs(projectDir));
if (files.isEmpty() == false) {
files.addAll(resolveDaemonLogs(daemonsLogDir));
files.addAll(getFileOperations().fileTree(gradleWorkersDir).getFiles());
files.addAll(getFileOperations().fileTree(new File(projectDir, ".gradle/reaper/")).getFiles());
}
return files;
}));
});
}
}

private List<File> resolveProjectLogs(File projectDir) {
var projectDirFiles = getFileOperations().fileTree(projectDir);
projectDirFiles.include("**/*.hprof");
projectDirFiles.include("**/build/test-results/**/*.xml");
projectDirFiles.include("**/build/testclusters/**");
projectDirFiles.include("**/build/testrun/*/temp/**");
projectDirFiles.include("**/build/**/hs_err_pid*.log");
projectDirFiles.exclude("**/build/testclusters/**/data/**");
projectDirFiles.exclude("**/build/testclusters/**/distro/**");
projectDirFiles.exclude("**/build/testclusters/**/repo/**");
projectDirFiles.exclude("**/build/testclusters/**/extract/**");
projectDirFiles.exclude("**/build/testclusters/**/tmp/**");
projectDirFiles.exclude("**/build/testrun/*/temp/**/data/**");
projectDirFiles.exclude("**/build/testrun/*/temp/**/distro/**");
projectDirFiles.exclude("**/build/testrun/*/temp/**/repo/**");
projectDirFiles.exclude("**/build/testrun/*/temp/**/extract/**");
projectDirFiles.exclude("**/build/testrun/*/temp/**/tmp/**");
return projectDirFiles.getFiles().stream().filter(f -> Files.isRegularFile(f.toPath())).toList();
}

private List<File> resolveDaemonLogs(File daemonsLogDir) {
var gradleDaemonFileSet = getFileOperations().fileTree(daemonsLogDir);
gradleDaemonFileSet.include("**/daemon-" + ProcessHandle.current().pid() + "*.log");
return gradleDaemonFileSet.getFiles().stream().filter(f -> Files.isRegularFile(f.toPath())).toList();
}

public abstract static class BuildFinishedFlowAction implements FlowAction<BuildFinishedFlowAction.Parameters> {
interface Parameters extends FlowParameters {
@Input
Property<File> getUploadFile();

@Input
Property<File> getProjectDir();

@Input
ListProperty<File> getFilteredFiles();

@Input
Property<BuildScanExtension> getBuildScan();

}

@Inject
protected abstract FileSystemOperations getFileSystemOperations();

@SuppressWarnings("checkstyle:DescendantToken")
@Override
public void execute(BuildFinishedFlowAction.Parameters parameters) throws FileNotFoundException {
File uploadFile = parameters.getUploadFile().get();
if (uploadFile.exists()) {
getFileSystemOperations().delete(spec -> spec.delete(uploadFile));
}
uploadFile.getParentFile().mkdirs();
createBuildArchiveTar(parameters.getFilteredFiles().get(), parameters.getProjectDir().get(), uploadFile);
if (uploadFile.exists() && System.getenv("BUILDKITE").equals("true")) {
String uploadFilePath = "build/" + uploadFile.getName();
try {
System.out.println("Uploading buildkite artifact: " + uploadFilePath + "...");
new ProcessBuilder("buildkite-agent", "artifact", "upload", uploadFilePath).start().waitFor();

System.out.println("Generating buildscan link for artifact...");

Process process = new ProcessBuilder(
"buildkite-agent",
"artifact",
"search",
uploadFilePath,
"--step",
System.getenv("BUILDKITE_JOB_ID"),
"--format",
"%i"
).start();
process.waitFor();
String processOutput;
try {
processOutput = IOUtils.toString(process.getInputStream());
} catch (IOException e) {
processOutput = "";
}
String artifactUuid = processOutput.trim();

System.out.println("Artifact UUID: " + artifactUuid);
if (artifactUuid.isEmpty() == false) {
String buildkitePipelineSlug = System.getenv("BUILDKITE_PIPELINE_SLUG");
String targetLink = "https://buildkite.com/organizations/elastic/pipelines/"
+ buildkitePipelineSlug
+ "/builds/"
+ System.getenv("BUILD_NUMBER")
+ "/jobs/"
+ System.getenv("BUILDKITE_JOB_ID")
+ "/artifacts/"
+ artifactUuid;
parameters.getBuildScan().get().link("Artifact Upload", targetLink);
}
} catch (Exception e) {
System.out.println("Failed to upload buildkite artifact " + e.getMessage());
}
}

}

private static void createBuildArchiveTar(List<File> files, File projectDir, File uploadFile) {
try (
OutputStream fOut = Files.newOutputStream(uploadFile.toPath());
BufferedOutputStream buffOut = new BufferedOutputStream(fOut);
BZip2CompressorOutputStream bzOut = new BZip2CompressorOutputStream(buffOut);
TarArchiveOutputStream tOut = new TarArchiveOutputStream(bzOut)
) {
Path projectPath = projectDir.toPath();
tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
tOut.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
for (Path path : files.stream().map(File::toPath).toList()) {
if (!Files.isRegularFile(path)) {
throw new IOException("Support only file!");
}

TarArchiveEntry tarEntry = new TarArchiveEntry(path.toFile(), calculateArchivePath(path, projectPath));
tarEntry.setSize(Files.size(path));
tOut.putArchiveEntry(tarEntry);

// copy file to TarArchiveOutputStream
Files.copy(path, tOut);
tOut.closeArchiveEntry();

}
tOut.flush();
tOut.finish();

} catch (IOException e) {
throw new RuntimeException(e);
}
}

@NotNull
private static String calculateArchivePath(Path path, Path projectPath) {
return path.startsWith(projectPath) ? projectPath.relativize(path).toString() : path.getFileName().toString();
}
}
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ plugins {
id 'lifecycle-base'
id 'elasticsearch.docker-support'
id 'elasticsearch.global-build-info'
id 'elasticsearch.build-scan'
id 'elasticsearch.build-complete'
id 'elasticsearch.build-scan'
id 'elasticsearch.jdk-download'
id 'elasticsearch.internal-distribution-download'
id 'elasticsearch.runtime-jdk-provision'
Expand Down
1 change: 1 addition & 0 deletions gradle/build.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ commons-codec = "commons-codec:commons-codec:1.11"
commmons-io = "commons-io:commons-io:2.2"
docker-compose = "com.avast.gradle:gradle-docker-compose-plugin:0.17.5"
forbiddenApis = "de.thetaphi:forbiddenapis:3.6"
gradle-enterprise = "com.gradle:gradle-enterprise-gradle-plugin:3.14.1"
hamcrest = "org.hamcrest:hamcrest:2.1"
httpcore = "org.apache.httpcomponents:httpcore:4.4.12"
httpclient = "org.apache.httpcomponents:httpclient:4.5.10"
Expand Down

0 comments on commit d6332a9

Please sign in to comment.