Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ build
pom.xml

dist
.cache

.openapi-generator

Expand Down
37 changes: 14 additions & 23 deletions scripts/buildSpecs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import fsp from 'fs/promises';

import { hashElement } from 'folder-hash';
import yaml from 'js-yaml';

import { exists, run, toAbsolutePath } from './common';
import { checkForCache, exists, run, toAbsolutePath } from './common';
import { createSpinner } from './oraLog';
import type { Spec } from './pre-gen/setHostsOptions';

Expand Down Expand Up @@ -53,30 +52,22 @@ async function buildSpec(
let hash = '';

if (useCache) {
const spinner = createSpinner(
`checking cache for '${client}'`,
const { cacheExists, hash: newCache } = await checkForCache(
{
job: `'${client}' specs`,
folder: toAbsolutePath('specs/'),
generatedFiles: [`bundled/${client}.yml`],
filesToCache: [client, 'common'],
cacheFile,
},
verbose
).start();
// check if file and cache exist
if (await exists(toAbsolutePath(`specs/bundled/${client}.yml`))) {
// compare with stored cache
const specHash = (await hashElement(toAbsolutePath(`specs/${client}`)))
.hash;
const commonHash = (await hashElement(toAbsolutePath(`specs/common`)))
.hash;
hash = `${specHash}-${commonHash}`;
if (await exists(cacheFile)) {
const storedHash = (await fsp.readFile(cacheFile)).toString();
if (storedHash === hash) {
spinner.succeed(
`skipped building ${client} spec because the files did not change`
);
return;
}
}
);

if (cacheExists) {
return;
}

spinner.info(`cache not found for ${client}' spec`);
hash = newCache;
}

const spinner = createSpinner(`building ${client} spec`, verbose).start();
Expand Down
77 changes: 76 additions & 1 deletion scripts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import fsp from 'fs/promises';
import path from 'path';

import execa from 'execa'; // https://github.com/sindresorhus/execa/tree/v5.1.1
import { hashElement } from 'folder-hash';

import openapitools from '../openapitools.json';

import { createSpinner } from './oraLog';
import type { Generator, RunOptions } from './types';
import type {
CheckForCache,
CheckForCacheOptions,
Generator,
RunOptions,
} from './types';

export const CI = Boolean(process.env.CI);
export const DOCKER = Boolean(process.env.DOCKER);
Expand Down Expand Up @@ -194,11 +200,80 @@ export async function gitCommit({
);
}

export async function checkForCache(
{
job,
folder,
generatedFiles,
filesToCache,
cacheFile,
}: CheckForCacheOptions,
verbose: boolean
): Promise<CheckForCache> {
const spinner = createSpinner(`checking cache for ${job}`, verbose).start();
const cache: CheckForCache = {
cacheExists: false,
hash: '',
};
const generatedFilesExists = (
await Promise.all(
generatedFiles.map((generatedFile) =>
exists(`${folder}/${generatedFile}`)
)
)
).every((exist) => exist);

for (const fileToCache of filesToCache) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be more elegant with map and join('-')

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But less readable :( I'd rather keep this one

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by elegant I meant readable, as you prefer

const fileHash = (await hashElement(`${folder}/${fileToCache}`)).hash;

cache.hash = `${cache.hash}-${fileHash}`;
}

// We only skip if both the cache and the generated file exists
if (generatedFilesExists && (await exists(cacheFile))) {
const storedHash = (await fsp.readFile(cacheFile)).toString();
if (storedHash === cache.hash) {
spinner.succeed(`job skipped, cache found for ${job}`);
return {
cacheExists: true,
hash: cache.hash,
};
}
}

spinner.info(`cache not found for ${job}`);

return cache;
}

export async function buildCustomGenerators(verbose: boolean): Promise<void> {
const cacheFile = toAbsolutePath('generators/.cache');
const { cacheExists, hash } = await checkForCache(
{
job: 'custom generators',
folder: toAbsolutePath('generators/'),
generatedFiles: ['build'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with only checking if the generatedFiles exists is that they are not just files, build is a folder and if you remove something in it, the cache skip but the gen won't work.
One way to solve it would be to enforce real files in the generatedFiles list, no folder, for example it would be build/libs/algolia-java-openapi-generator-1.0.0.jar.
But then it would make the cache harder to use, if we really want all the files under build for the cache to be valid, then we can also store a hash of the generated files and compare that

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let you choose what to do here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've went with the naive solution because I wanted to have it merged before the demo, but storing hash as we do on the CI might be the more precise solution.

Copy link
Member Author

@shortcuts shortcuts Mar 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fun fact, I had the local changes during the demo but built #313 before so it invalidated the cache and added like 5s to the demo 🤷🏼

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah on the CI they store a zip of the generated files it's even more hardcore

filesToCache: ['src', 'build.gradle', 'settings.gradle'],
cacheFile,
},
verbose
);

if (cacheExists) {
return;
}

const spinner = createSpinner('building custom generators', verbose).start();

await run('./gradle/gradlew --no-daemon -p generators assemble', {
verbose,
});

if (hash) {
spinner.text = 'storing custom generators cache';
await fsp.writeFile(cacheFile, hash);
}

spinner.succeed();
}

Expand Down
13 changes: 13 additions & 0 deletions scripts/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import type config from '../config/clients.config.json';

export type CheckForCacheOptions = {
job: string;
folder: string;
generatedFiles: string[];
filesToCache: string[];
cacheFile: string;
};

export type CheckForCache = {
cacheExists: boolean;
hash: string;
};

export type Generator = Record<string, any> & {
language: string;
client: string;
Expand Down