Skip to content

Commit

Permalink
#519: Replace default build step
Browse files Browse the repository at this point in the history
  • Loading branch information
kaklakariada committed Mar 27, 2024
1 parent 8aae710 commit 2b75c6d
Show file tree
Hide file tree
Showing 17 changed files with 507 additions and 176 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Sonar analysis
id: sonar-analysis
if: ${{ env.SONAR_TOKEN != null }}
run: |
mvn --batch-mode org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
Expand All @@ -69,7 +70,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Run project-keeper itself
id: pk-verify
id: build-pk-verify
run: mvn --batch-mode com.exasol:project-keeper-maven-plugin:verify --projects .

- name: Verify Release Artifacts
Expand Down Expand Up @@ -103,13 +104,13 @@ jobs:
exit 1
fi
env:
ARTIFACTS: ${{ steps.pk-verify.outputs.release-artifacts }}
ARTIFACTS: ${{ steps.build-pk-verify.outputs.release-artifacts }}

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts
path: ${{ steps.pk-verify.outputs.release-artifacts }}
path: ${{ steps.build-pk-verify.outputs.release-artifacts }}
retention-days: 5

- name: Check if release is needed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test_on_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Build and install
run: mvn --batch-mode -T 1C -DtrimStackTrace=false clean install
- name: Run project-keeper itself
id: pk-verify
id: build-pk-verify
run: mvn --batch-mode -DtrimStackTrace=false com.exasol:project-keeper-maven-plugin:verify --projects .

- name: Verify Release Artifacts
Expand Down Expand Up @@ -80,4 +80,4 @@ jobs:
exit 1
fi
env:
ARTIFACTS: ${{ steps.pk-verify.outputs.release-artifacts }}
ARTIFACTS: ${{ steps.build-pk-verify.outputs.release-artifacts }}
106 changes: 54 additions & 52 deletions dependencies.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions doc/changes/changes_4.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ This release contains many new features and improvements:
#### Compile Dependency Updates

* Updated `com.exasol:project-keeper-shared-model-classes:4.2.0` to `4.3.0`
* Added `org.snakeyaml:snakeyaml-engine:2.7`

#### Runtime Dependency Updates

Expand Down
5 changes: 5 additions & 0 deletions parent-pom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.snakeyaml</groupId>
<artifactId>snakeyaml-engine</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-github</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions project-keeper/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.snakeyaml</groupId>
<artifactId>snakeyaml-engine</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,50 @@
import com.exasol.projectkeeper.shared.config.BuildOptions;

class CiBuildWorkflowGenerator {
private static final String PATH_IN_PROJECT = ".github/workflows/ci-build.yml";
private final BuildOptions buildOptions;

CiBuildWorkflowGenerator(final BuildOptions buildOptions) {
this.buildOptions = buildOptions;
}

FileTemplate createCiBuildWorkflow() {
final FileTemplateFromResource template = new FileTemplateFromResource(
"templates/.github/workflows/" + getCiBuildTemplate(), ".github/workflows/ci-build.yml", REQUIRE_EXACT)
.replacing("ciBuildRunnerOS", buildOptions.getRunnerOs())
.replacing("freeDiskSpace", String.valueOf(buildOptions.shouldFreeDiskSpace()));

if (isMatrixBuild()) {
final CiTemplateType buildType = getCiBuildType();
final String templateResource = "templates/.github/workflows/" + buildType.templateName;
final FileTemplateFromResource template = new FileTemplateFromResource(templateResource, PATH_IN_PROJECT,
REQUIRE_EXACT);
template.replacing("ciBuildRunnerOS", buildOptions.getRunnerOs());
template.replacing("freeDiskSpace", String.valueOf(buildOptions.shouldFreeDiskSpace()));

if (buildType == CiTemplateType.EXASOL_VERSION_MATRIX) {
template.replacing("matrixExasolDbVersions",
buildOptions.getExasolDbVersions().stream().map(this::quote).collect(joining(", ")));
template.replacing("defaultExasolDbVersion", quote(buildOptions.getExasolDbVersions().get(0)));
}
return new ContentCustomizingTemplate(template, new GitHubWorkflowStepCustomizer(buildOptions));
return new ContentCustomizingTemplate(template,
new GitHubWorkflowStepCustomizer(buildOptions, buildType.buildJobId));
}

private String quote(final String value) {
return '"' + value + '"';
}

private String getCiBuildTemplate() {
if (isMatrixBuild()) {
return "ci-build-db-version-matrix.yml";
} else {
return "ci-build.yml";
private CiTemplateType getCiBuildType() {
if (!buildOptions.getExasolDbVersions().isEmpty()) {
return CiTemplateType.EXASOL_VERSION_MATRIX;
}
return CiTemplateType.DEFAULT;
}

private boolean isMatrixBuild() {
return !buildOptions.getExasolDbVersions().isEmpty();
enum CiTemplateType {
DEFAULT("ci-build.yml", "build"), EXASOL_VERSION_MATRIX("ci-build-db-version-matrix.yml", "matrix-build");

private final String templateName;
private final String buildJobId;

private CiTemplateType(final String templateName, final String buildJobId) {
this.templateName = templateName;
this.buildJobId = buildJobId;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.exasol.projectkeeper.validators.files;

import static java.util.Collections.emptyList;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;

class GitHubWorkflow {

private final Map<String, Object> rawWorkflow;

GitHubWorkflow(final Map<String, Object> rawWorkflow) {
this.rawWorkflow = rawWorkflow;
}

static GitHubWorkflow create(final Object rawWorkflow) {
return new GitHubWorkflow(asMap(rawWorkflow));
}

Job getJob(final String jobId) {
final Map<String, Object> jobs = asMap(rawWorkflow.get("jobs"));
return new Job(asMap(jobs.get(jobId)));
}

Map<String, Object> getOnTrigger() {
return asMap(rawWorkflow.get("on"));
}

Object getRawObject() {
return this.rawWorkflow;
}

@SuppressWarnings("unchecked")
private static Map<String, Object> asMap(final Object rawYaml) {
return (Map<String, Object>) rawYaml;
}

static class Job {
private final Map<String, Object> rawJob;

Job(final Map<String, Object> rawJob) {
this.rawJob = rawJob;
}

Step getStep(final String id) {
return getSteps().stream() //
.filter(hasId(id)) //
.findFirst() //
.orElseThrow(
() -> new IllegalStateException("Job has no step with ID '" + id + "': " + getRawSteps()));
}

List<Step> getSteps() {
return getRawSteps().stream().map(Step::new).toList();
}

@SuppressWarnings("unchecked")
private List<Map<String, Object>> getRawSteps() {
final Object steps = rawJob.get("steps");
if (steps == null) {
return emptyList();
}
return (List<Map<String, Object>>) steps;
}

String getRunnerOS() {
return (String) rawJob.get("runs-on");
}

Map<String, Object> getConcurrency() {
return asMap(rawJob.get("concurrency"));
}

Map<String, Object> getStrategy() {
return asMap(rawJob.get("strategy"));
}

Map<String, Object> getEnv() {
return asMap(rawJob.get("env"));
}

private Predicate<? super Step> hasId(final String id) {
return step -> step.getId().isPresent() && step.getId().get().equals(id);
}

public void replaceStep(final String stepId, final Map<String, Object> rawStep) {
final List<Map<String, Object>> allSteps = getRawSteps();
final int index = findStepIndex(stepId);
allSteps.set(index, rawStep);
}

private int findStepIndex(final String stepId) {
final List<Step> steps = getSteps();
return IntStream.range(0, steps.size()) //
.filter(index -> steps.get(index).getId().get().equals(stepId)) //
.findFirst() //
.orElseThrow(() -> new IllegalStateException("No step found for id '" + stepId + "' in " + rawJob));
}
}

static class Step {
private final Map<String, Object> rawStep;

Step(final Map<String, Object> rawStep) {
this.rawStep = rawStep;
}

Optional<String> getId() {
return getOptionalString("id");
}

Optional<String> getOptionalString(final String key) {
return Optional.ofNullable((String) rawStep.get(key));
}

String getString(final String key) {
return getOptionalString(key)
.orElseThrow(() -> new IllegalStateException("Step has no field '" + key + "': " + rawStep));
}

String getName() {
return getString("name");
}

String getRunCommand() {
return getString("run");
}

String getIfCondition() {
return getString("if");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,60 +1,37 @@
package com.exasol.projectkeeper.validators.files;

import org.yaml.snakeyaml.*;
import org.yaml.snakeyaml.DumperOptions.LineBreak;
import org.yaml.snakeyaml.DumperOptions.NonPrintableStyle;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
import java.util.Optional;

import com.exasol.projectkeeper.shared.config.BuildOptions;
import com.exasol.projectkeeper.shared.config.WorkflowStep;

class GitHubWorkflowStepCustomizer implements ContentCustomizingTemplate.ContentCustomizer {

private final BuildOptions buildOptions;
private final Yaml yaml;
private final YamlIO yaml;
private final String jobId;

GitHubWorkflowStepCustomizer(final BuildOptions buildOptions) {
this(configureYaml(), buildOptions);
GitHubWorkflowStepCustomizer(final BuildOptions buildOptions, final String jobId) {
this(YamlIO.create(), buildOptions, jobId);
}

GitHubWorkflowStepCustomizer(final Yaml yaml, final BuildOptions buildOptions) {
GitHubWorkflowStepCustomizer(final YamlIO yaml, final BuildOptions buildOptions, final String jobId) {
this.yaml = yaml;
this.buildOptions = buildOptions;
this.jobId = jobId;
}

@Override
public String customizeContent(final String content) {
final Object object = yaml.load(content);
return yaml.dump(customizeWorkflow(object));
final GitHubWorkflow workflow = yaml.loadGitHubWorkflow(content);
customizeWorkflow(workflow);
return yaml.dumpWorkflow(workflow);
}

private Object customizeWorkflow(final Object object) {
return object;
}

private static Yaml configureYaml() {
final DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setIndent(2);
dumperOptions.setCanonical(false);
dumperOptions.setExplicitEnd(false);
dumperOptions.setExplicitStart(false);
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO); // Remove quotes
dumperOptions.setPrettyFlow(true); // Remove curly brackets
dumperOptions.setLineBreak(LineBreak.UNIX);
dumperOptions.setSplitLines(false); // Remove line breaks
dumperOptions.setWidth(400);
dumperOptions.setProcessComments(true);
dumperOptions.setAllowUnicode(true);
dumperOptions.setIndentWithIndicator(false);
dumperOptions.setIndicatorIndent(2);
dumperOptions.setMaxSimpleKeyLength(128);
dumperOptions.setNonPrintableStyle(NonPrintableStyle.ESCAPE);

final LoaderOptions loaderConfig = new LoaderOptions();
loaderConfig.setProcessComments(true);
loaderConfig.setAllowDuplicateKeys(false);
loaderConfig.setAllowRecursiveKeys(false);
loaderConfig.setWrappedToRootException(true);
return new Yaml(new Constructor(loaderConfig), new Representer(dumperOptions));
private void customizeWorkflow(final GitHubWorkflow workflow) {
final Optional<WorkflowStep> customStep = buildOptions.getBuildStep();
if (customStep.isPresent()) {
workflow.getJob(jobId).replaceStep("build-pk-verify", customStep.get().getRawStep());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.exasol.projectkeeper.validators.files;

import org.snakeyaml.engine.v2.api.*;
import org.snakeyaml.engine.v2.common.*;

class YamlIO {

private final Load loader;
private final Dump dumper;

private YamlIO(final Load loader, final Dump dumper) {
this.loader = loader;
this.dumper = dumper;
}

static YamlIO create() {
final LoadSettings loadSettings = LoadSettings.builder().setParseComments(true).build();
final DumpSettings dumpSettings = DumpSettings.builder().setBestLineBreak("\n").setCanonical(false)
.setDefaultFlowStyle(FlowStyle.AUTO).setDefaultScalarStyle(ScalarStyle.PLAIN).setDumpComments(true)
.setExplicitEnd(false).setExplicitStart(false) //
.setIndent(2).setIndentWithIndicator(true).setIndicatorIndent(2) //
.setWidth(200) //
.setMultiLineFlow(true).setNonPrintableStyle(NonPrintableStyle.ESCAPE).setUseUnicodeEncoding(true)
.setSplitLines(false).build();
return new YamlIO(new Load(loadSettings), new Dump(dumpSettings));
}

GitHubWorkflow loadGitHubWorkflow(final String content) {
return GitHubWorkflow.create(load(content));
}

Object load(final String content) {
return this.loader.loadFromString(content);
}

String dumpWorkflow(final GitHubWorkflow workflow) {
return dump(workflow.getRawObject());
}

String dump(final Object object) {
return this.dumper.dumpToString(object);
}
}
Loading

0 comments on commit 2b75c6d

Please sign in to comment.