Skip to content

Commit dc78405

Browse files
john lilleywing328
john lilley
authored andcommitted
1391 jel minimal overwrite option (#2451)
Option to overwrite only changed files
1 parent fef2970 commit dc78405

File tree

7 files changed

+151
-15
lines changed

7 files changed

+151
-15
lines changed

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ public class Generate implements Runnable {
217217
@Option(name = {"--generate-alias-as-model"}, title = "generate alias (array, map) as model", description = CodegenConstants.GENERATE_ALIAS_AS_MODEL_DESC)
218218
private Boolean generateAliasAsModel;
219219

220+
@Option(name = {"--minimal-update"},
221+
title = "Minimal update",
222+
description = "Only write output files that have changed.")
223+
private Boolean minimalUpdate;
224+
220225
@Override
221226
public void run() {
222227
if (logToStderr != null) {
@@ -346,6 +351,9 @@ public void run() {
346351
if (generateAliasAsModel != null) {
347352
configurator.setGenerateAliasAsModel(generateAliasAsModel);
348353
}
354+
if (minimalUpdate != null) {
355+
configurator.setEnableMinimalUpdate(minimalUpdate);
356+
}
349357

350358
applySystemPropertiesKvpList(systemProperties, configurator);
351359
applyInstantiationTypesKvpList(instantiationTypes, configurator);

modules/openapi-generator/src/main/java/org/openapitools/codegen/AbstractGenerator.java

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
package org.openapitools.codegen;
1919

20+
import java.nio.charset.Charset;
21+
import java.nio.file.Files;
22+
import java.nio.file.StandardCopyOption;
23+
import java.util.Arrays;
2024
import org.apache.commons.lang3.StringUtils;
2125
import org.slf4j.Logger;
2226
import org.slf4j.LoggerFactory;
@@ -28,23 +32,78 @@
2832

2933
public abstract class AbstractGenerator {
3034
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGenerator.class);
35+
36+
/**
37+
* Is the minimal-file-update option enabled?
38+
*
39+
* @return Option value
40+
*/
41+
public abstract boolean getEnableMinimalUpdate();
3142

32-
@SuppressWarnings("static-method")
43+
/**
44+
* Write String to a file, formatting as UTF-8
45+
*
46+
* @param filename The name of file to write
47+
* @param contents The contents string.
48+
* @return File representing the written file.
49+
* @throws IOException If file cannot be written.
50+
*/
3351
public File writeToFile(String filename, String contents) throws IOException {
34-
LOGGER.info("writing file " + filename);
52+
return writeToFile(filename, contents.getBytes(Charset.forName("UTF-8")));
53+
}
3554

55+
/**
56+
* Write bytes to a file
57+
*
58+
* @param filename The name of file to write
59+
* @param contents The contents bytes. Typically this is a UTF-8 formatted string.
60+
* @return File representing the written file.
61+
* @throws IOException If file cannot be written.
62+
*/
63+
@SuppressWarnings("static-method")
64+
public File writeToFile(String filename, byte contents[]) throws IOException {
65+
if (getEnableMinimalUpdate()) {
66+
String tempFilename = filename + ".tmp";
67+
// Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc)
68+
File outputFile = Paths.get(filename).toFile();
69+
File tempFile = null;
70+
try {
71+
tempFile = writeToFileRaw(tempFilename, contents);
72+
if (!filesEqual(tempFile, outputFile)) {
73+
LOGGER.info("writing file " + filename);
74+
Files.move(tempFile.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
75+
tempFile = null;
76+
} else {
77+
LOGGER.info("skipping unchanged file " + filename);
78+
}
79+
} finally {
80+
if (tempFile != null && tempFile.exists()) {
81+
try {
82+
tempFile.delete();
83+
} catch (Exception ex) {
84+
LOGGER.error("Error removing temporary file " + tempFile, ex);
85+
}
86+
}
87+
}
88+
return outputFile;
89+
} else {
90+
LOGGER.info("writing file " + filename);
91+
return writeToFileRaw(filename, contents);
92+
}
93+
}
94+
95+
private boolean filesEqual(File file1, File file2) throws IOException {
96+
return file1.exists() && file2.exists() && Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
97+
}
98+
99+
private File writeToFileRaw(String filename, byte[] contents) throws IOException {
36100
// Use Paths.get here to normalize path (for Windows file separator, space escaping on Linux/Mac, etc)
37101
File output = Paths.get(filename).toFile();
38-
39102
if (output.getParent() != null && !new File(output.getParent()).exists()) {
40-
File parent = new File(output.getParent());
103+
File parent = Paths.get(output.getParent()).toFile();
41104
parent.mkdirs();
42105
}
43-
44-
try (Writer out = new BufferedWriter(new OutputStreamWriter(
45-
new FileOutputStream(output), "UTF-8"))) {
46-
out.write(contents);
47-
}
106+
Files.write(output.toPath(), contents);
48107
return output;
49108
}
50109

modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,8 @@ public interface CodegenConfig {
260260
*/
261261
void setOpenAPI(OpenAPI openAPI);
262262

263+
public boolean isEnableMinimalUpdate();
264+
265+
public void setEnableMinimalUpdate(boolean isEnableMinimalUpdate);
266+
263267
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public class DefaultCodegen implements CodegenConfig {
111111
protected String ignoreFilePathOverride;
112112
// flag to indicate whether to use environment variable to post process file
113113
protected boolean enablePostProcessFile = false;
114+
// flag to indicate whether to only update files whose contents have changed
115+
protected boolean enableMinimalUpdate = false;
114116

115117
// make openapi available to all methods
116118
protected OpenAPI openAPI;
@@ -4834,4 +4836,22 @@ public void setEnablePostProcessFile(boolean enablePostProcessFile) {
48344836
this.enablePostProcessFile = enablePostProcessFile;
48354837
}
48364838

4839+
/**
4840+
* Get the boolean value indicating the state of the option for updating only changed files
4841+
*/
4842+
@Override
4843+
public boolean isEnableMinimalUpdate() {
4844+
return enableMinimalUpdate;
4845+
}
4846+
4847+
/**
4848+
* Set the boolean value indicating the state of the option for updating only changed files
4849+
*
4850+
* @param enableMinimalUpdate true to enable minimal update
4851+
*/
4852+
@Override
4853+
public void setEnableMinimalUpdate(boolean enableMinimalUpdate) {
4854+
this.enableMinimalUpdate = enableMinimalUpdate;
4855+
}
4856+
48374857
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
6767
private String contextPath;
6868
private Map<String, String> generatorPropertyDefaults = new HashMap<>();
6969

70+
@Override
71+
public boolean getEnableMinimalUpdate() {
72+
return config.isEnableMinimalUpdate();
73+
}
74+
7075
@Override
7176
public Generator opts(ClientOptInput opts) {
7277
this.opts = opts;
@@ -797,16 +802,13 @@ public Reader getTemplate(String name) {
797802
}
798803

799804
protected File writeInputStreamToFile(String filename, InputStream in, String templateFile) throws FileNotFoundException, IOException {
800-
File outputFile = java.nio.file.Paths.get(filename).toFile();
801805
if (in != null) {
802-
OutputStream out = new FileOutputStream(outputFile, false);
803-
LOGGER.info("writing file " + outputFile);
804-
IOUtils.copy(in, out);
805-
out.close();
806+
byte bytes[] = IOUtils.toByteArray(in);
807+
return writeToFile(filename, bytes);
806808
} else {
807809
LOGGER.error("can't open '" + templateFile + "' for input; cannot write '" + filename + "'");
810+
return null;
808811
}
809-
return outputFile;
810812
}
811813

812814
private Map<String, Object> buildSupportFileBundle(List<Object> allOperations, List<Object> allModels) {

modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public class CodegenConfigurator implements Serializable {
101101
private boolean logToStderr;
102102
private boolean validateSpec;
103103
private boolean enablePostProcessFile;
104+
private boolean enableMinimalUpdate;
104105
private String templateDir;
105106
private String auth;
106107
private String apiPackage;
@@ -239,6 +240,15 @@ public CodegenConfigurator setLogToStderr(boolean logToStderrte) {
239240
return this;
240241
}
241242

243+
public boolean getEnableMinimalUpdate() {
244+
return enableMinimalUpdate;
245+
}
246+
247+
public CodegenConfigurator setEnableMinimalUpdate(boolean enableMinimalUpdate) {
248+
this.enableMinimalUpdate = enableMinimalUpdate;
249+
return this;
250+
}
251+
242252
public boolean isGenerateAliasAsModel() {
243253
return ModelUtils.isGenerateAliasAsModel();
244254
}
@@ -545,6 +555,7 @@ public ClientOptInput toClientOptInput() {
545555
config.setIgnoreFilePathOverride(ignoreFileOverride);
546556
config.setRemoveOperationIdPrefix(removeOperationIdPrefix);
547557
config.setEnablePostProcessFile(enablePostProcessFile);
558+
config.setEnableMinimalUpdate(enableMinimalUpdate);
548559

549560
config.instantiationTypes().putAll(instantiationTypes);
550561
config.typeMapping().putAll(typeMappings);

modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultGeneratorTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
import io.swagger.v3.oas.models.parameters.QueryParameter;
1010
import io.swagger.v3.oas.models.responses.ApiResponse;
1111
import io.swagger.v3.oas.models.responses.ApiResponses;
12+
import java.io.File;
13+
import java.io.IOException;
1214
import org.testng.Assert;
1315
import org.testng.annotations.Test;
1416

1517
import java.util.List;
1618
import java.util.Map;
19+
import java.util.logging.Level;
20+
import java.util.logging.Logger;
1721

1822
public class DefaultGeneratorTest {
1923

@@ -46,4 +50,32 @@ public void testProcessPaths() throws Exception {
4650
Assert.assertEquals(defaultList.get(3).path, "/path4");
4751
Assert.assertEquals(defaultList.get(3).allParams.size(), 1);
4852
}
53+
54+
@Test
55+
public void minimalUpdateTest() throws IOException {
56+
OpenAPI openAPI = TestUtils.createOpenAPI();
57+
ClientOptInput opts = new ClientOptInput();
58+
opts.setOpenAPI(openAPI);
59+
DefaultCodegen codegen = new DefaultCodegen();
60+
codegen.setEnableMinimalUpdate(true);
61+
opts.setConfig(codegen);
62+
opts.setOpts(new ClientOpts());
63+
DefaultGenerator generator = new DefaultGenerator();
64+
generator.opts(opts);
65+
File testPath = new File("temp/overwrite.test");
66+
if (testPath.exists()) {
67+
testPath.delete();
68+
}
69+
generator.writeToFile(testPath.toString(), "some file contents");
70+
long createTime = testPath.lastModified();
71+
try {
72+
Thread.sleep(100);
73+
} catch (InterruptedException ex) {
74+
}
75+
generator.writeToFile(testPath.toString(), "some file contents");
76+
Assert.assertEquals(createTime, testPath.lastModified());
77+
File testPathTmp = new File("temp/overwrite.test.tmp");
78+
Assert.assertFalse(testPathTmp.exists());
79+
testPath.delete();
80+
}
4981
}

0 commit comments

Comments
 (0)