1616package com .coinbase .tools .modelgenerator ;
1717
1818import java .io .IOException ;
19+ import java .nio .charset .StandardCharsets ;
1920import java .nio .file .Files ;
2021import java .nio .file .Path ;
2122import java .util .ArrayList ;
23+ import java .util .Collections ;
2224import java .util .LinkedHashMap ;
2325import java .util .List ;
2426import java .util .Map ;
@@ -54,10 +56,13 @@ static void run(String[] args, GeneratorPaths paths) throws Exception {
5456 skipModels = true ;
5557 }
5658
57- List <String > changes = new ArrayList <>();
5859 if (check && !liveDiff ) {
59- changes .addAll (checkModelsInIsolation (paths , spec ));
60- } else if (!skipModels && !check ) {
60+ List <String > changes = checkGeneratedInIsolation (paths , spec , configuration );
61+ reportChanges (changes );
62+ return ;
63+ }
64+
65+ if (!skipModels && !check ) {
6166 new OpenApiGenerator (spec .toString (), paths .rawRoot ()).generateModels ();
6267 new PostProcessor (
6368 paths .rawRoot (),
@@ -70,63 +75,141 @@ static void run(String[] args, GeneratorPaths paths) throws Exception {
7075 .processModels ();
7176 }
7277
73- SpecModels .Document document = SpecParser .load (spec );
74- NamingResolver names =
75- new NamingResolver (configuration .nameReplacements (), configuration .modelTypeMappings ());
76- List <OperationBinding > bindings = OperationBindingGenerator .deriveAll (document , configuration );
77- JavaTypeResolver types = new JavaTypeResolver (document , names , configuration .sharedModelMappings ());
78- Map <Path , String > sources = new LinkedHashMap <>();
79- sources .putAll (RequestPhase .render (document , bindings , types , names ));
80- sources .putAll (ResponsePhase .render (document , bindings , types , names ));
81- sources .putAll (ServicePhase .render (document , bindings , configuration , names ));
82- sources .putAll (FactoryPhase .render (bindings ));
83- changes .addAll (
78+ Map <Path , String > sources = renderClientSources (spec , configuration );
79+ List <String > changes =
8480 GeneratedSourceReconciler .diff (
85- paths .sourceRoot (), sources , configuration .protectedFiles (), paths .manifest ())) ;
81+ paths .sourceRoot (), sources , configuration .protectedFiles (), paths .manifest ());
8682
8783 if (check ) {
88- for (String change : changes ) {
89- System .out .println (change );
90- }
91- if (!changes .isEmpty ()) {
92- throw new IllegalStateException ("Generated source is out of date (" + changes .size () + " changes)" );
93- }
84+ reportChanges (changes );
9485 } else {
9586 GeneratedSourceReconciler .write (
9687 paths .sourceRoot (), sources , configuration .protectedFiles (), paths .manifest ());
9788 System .out .println ("Generated " + sources .size () + " client-surface files" );
9889 }
9990 }
10091
101- /** Renders models into a temporary source tree so checks never touch committed SDK files. */
102- static List <String > checkModelsInIsolation (GeneratorPaths paths , Path spec ) throws Exception {
103- Files .createDirectories (paths .rawRoot ());
104- Path stagingRoot = Files .createTempDirectory (paths .rawRoot (), "check-models-" );
92+ /**
93+ * Runs the complete write-mode pipeline against a disposable copy of the source tree.
94+ *
95+ * <p>The staged project receives the same Spotless normalization as {@code make generate}; only its
96+ * rendered, manifest-owned files are compared with the repository. The repository itself is never
97+ * used as an output directory in check mode.
98+ */
99+ static List <String > checkGeneratedInIsolation (
100+ GeneratorPaths paths , Path spec , GeneratorConfiguration configuration ) throws Exception {
101+ Path stagingRoot = Files .createTempDirectory ("prime-sdk-java-generator-check-" );
105102 try {
106- Path rawGenerationRoot = stagingRoot .resolve ("raw-generation" );
107103 Path stagedSourceRoot = stagingRoot .resolve ("src/main/java" );
108104 Path stagedModelRoot = stagedSourceRoot .resolve ("com/coinbase/prime/model" );
109- Path stagedManifest = stagingRoot .resolve ("generated-model-files.json" );
110- new OpenApiGenerator (spec .toString (), rawGenerationRoot ).generateModels ();
105+ Path stagedModelManifest = stagingRoot .resolve ("tools/model-generator/generated-model-files.json" );
106+ Path stagedClientManifest = stagingRoot .resolve ("tools/model-generator/generated-files.json" );
107+
108+ copyProjectInputs (paths , stagingRoot , stagedSourceRoot , stagedModelManifest , stagedClientManifest );
109+ Path stagedRawRoot = stagingRoot .resolve ("generated" );
110+ new OpenApiGenerator (spec .toString (), stagedRawRoot , paths .root ()).generateModels ();
111111 new PostProcessor (
112- rawGenerationRoot ,
112+ stagedRawRoot ,
113113 stagedSourceRoot ,
114114 stagedModelRoot ,
115115 stagedModelRoot .resolve ("enums" ),
116116 stagedModelRoot .resolve ("errors" ),
117117 spec ,
118- stagedManifest )
118+ stagedModelManifest )
119119 .processModels ();
120- return GeneratedSourceReconciler .diff (
121- paths .sourceRoot (),
122- GeneratedSourceReconciler .readOwnedSources (stagedSourceRoot , stagedManifest ),
123- java .util .Collections .emptySet (),
124- paths .modelManifest ());
120+ GeneratedSourceReconciler .write (
121+ stagedSourceRoot ,
122+ renderClientSources (spec , configuration ),
123+ configuration .protectedFiles (),
124+ stagedClientManifest );
125+ formatStagedSources (stagingRoot );
126+
127+ List <String > changes = new ArrayList <>();
128+ changes .addAll (
129+ GeneratedSourceReconciler .diff (
130+ paths .sourceRoot (),
131+ GeneratedSourceReconciler .readOwnedSources (stagedSourceRoot , stagedModelManifest ),
132+ Collections .emptySet (),
133+ paths .modelManifest ()));
134+ changes .addAll (
135+ GeneratedSourceReconciler .diff (
136+ paths .sourceRoot (),
137+ GeneratedSourceReconciler .readOwnedSources (stagedSourceRoot , stagedClientManifest ),
138+ configuration .protectedFiles (),
139+ paths .manifest ()));
140+ addManifestChangeIfPresent (changes , paths .modelManifest (), stagedModelManifest );
141+ addManifestChangeIfPresent (changes , paths .manifest (), stagedClientManifest );
142+ Collections .sort (changes );
143+ return Collections .unmodifiableList (changes );
125144 } finally {
126145 FileUtils .deleteDirectory (stagingRoot .toFile ());
127146 }
128147 }
129148
149+ private static void copyProjectInputs (
150+ GeneratorPaths paths ,
151+ Path stagingRoot ,
152+ Path stagedSourceRoot ,
153+ Path stagedModelManifest ,
154+ Path stagedClientManifest )
155+ throws IOException {
156+ Files .copy (paths .root ().resolve ("pom.xml" ), stagingRoot .resolve ("pom.xml" ));
157+ FileUtils .copyDirectory (paths .sourceRoot ().toFile (), stagedSourceRoot .toFile ());
158+ copyIfPresent (paths .modelManifest (), stagedModelManifest );
159+ copyIfPresent (paths .manifest (), stagedClientManifest );
160+ }
161+
162+ private static void copyIfPresent (Path source , Path target ) throws IOException {
163+ if (Files .exists (source )) {
164+ Files .createDirectories (target .getParent ());
165+ Files .copy (source , target );
166+ }
167+ }
168+
169+ private static void formatStagedSources (Path stagingRoot ) throws IOException , InterruptedException {
170+ Process process =
171+ new ProcessBuilder (
172+ "mvn" , "-B" , "-f" , stagingRoot .resolve ("pom.xml" ).toString (), "spotless:apply" )
173+ .redirectErrorStream (true )
174+ .start ();
175+ String output = new String (process .getInputStream ().readAllBytes (), StandardCharsets .UTF_8 );
176+ if (process .waitFor () != 0 ) {
177+ throw new IOException ("Could not format staged generated sources:\n " + output );
178+ }
179+ }
180+
181+ private static void addManifestChangeIfPresent (
182+ List <String > changes , Path committedManifest , Path stagedManifest ) throws IOException {
183+ if (Files .exists (committedManifest )
184+ && !Files .readString (committedManifest ).equals (Files .readString (stagedManifest ))) {
185+ changes .add ("CHANGE " + committedManifest .getFileName ());
186+ }
187+ }
188+
189+ private static Map <Path , String > renderClientSources (
190+ Path spec , GeneratorConfiguration configuration ) throws IOException {
191+ SpecModels .Document document = SpecParser .load (spec );
192+ NamingResolver names =
193+ new NamingResolver (configuration .nameReplacements (), configuration .modelTypeMappings ());
194+ List <OperationBinding > bindings = OperationBindingGenerator .deriveAll (document , configuration );
195+ JavaTypeResolver types = new JavaTypeResolver (document , names , configuration .sharedModelMappings ());
196+ Map <Path , String > sources = new LinkedHashMap <>();
197+ sources .putAll (RequestPhase .render (document , bindings , types , names ));
198+ sources .putAll (ResponsePhase .render (document , bindings , types , names ));
199+ sources .putAll (ServicePhase .render (document , bindings , configuration , names ));
200+ sources .putAll (FactoryPhase .render (bindings ));
201+ return sources ;
202+ }
203+
204+ private static void reportChanges (List <String > changes ) {
205+ for (String change : changes ) {
206+ System .out .println (change );
207+ }
208+ if (!changes .isEmpty ()) {
209+ throw new IllegalStateException ("Generated source is out of date (" + changes .size () + " changes)" );
210+ }
211+ }
212+
130213 private static boolean has (String [] args , String value ) {
131214 for (String arg : args ) {
132215 if (value .equals (arg )) {
0 commit comments