Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for flavored plans in recipe2plan. #6997

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
87 changes: 87 additions & 0 deletions src/tools/flavored-plan-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @license
* Copyright 2020 Google LLC.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import {Recipe} from '../runtime/recipe/lib-recipe.js';
import {KotlinGenerationUtils, upperFirst, tryImport} from './kotlin-generation-utils.js';
import {PlanGenerator} from './plan-generator.js';
import {Dictionary} from '../utils/lib-utils.js';

const ktUtils = new KotlinGenerationUtils();

export class FlavoredPlanGenerator {
constructor(private flavoredRecipes: Dictionary<Recipe[]>,
private resolvedRecipeNames: Set<string>,
private namespace: string,
private flavorSelector: string
) {}

/** Generates a Kotlin file with plan classes derived from resolved recipes. */
async generate(): Promise<string> {
const emptyPlanGenerator = new PlanGenerator([], this.namespace);

const planOutline = [
emptyPlanGenerator.fileHeader(),
...(await this.createPlans()),
'\n',
...(await this.createPlanSelectors()),
emptyPlanGenerator.fileFooter()
];

return planOutline.join('\n');
}

async createPlans(): Promise<string[]> {
const allPlans: string[] = [];
for (const flavor of Object.keys(this.flavoredRecipes)) {
const recipe = this.flavoredRecipes[flavor];
const planGenerator = new PlanGenerator(recipe, this.namespace);
// const plan = (await planGenerator.createPlans()).join('\n');
// ktUtils.indent(plan, 1),
allPlans.push(
`class ${this.flavorClass(flavor)} {`,
` companion object {`,
ktUtils.joinWithIndents(
await planGenerator.createPlans(),
{startIndent: 0, numberOfIndents: 1}),
' }',
`}\n`,
);
}
return allPlans;
}

private flavorClass(flavor: string): string {
return `${upperFirst(flavor)}Plans`;
}

async createPlanSelectors(): Promise<string[]> {
const flavors = Object.keys(this.flavoredRecipes);
const allPlans: string[] = [];
for (const recipeName of this.resolvedRecipeNames) {
allPlans.push(
await this.createSelectorExpression(recipeName, flavors));
}
return allPlans;
}

private async createSelectorExpression(
recipeName: string, flavors: string[]): Promise<string> {
const planName = `${recipeName}Plan`;
return ktUtils.property(`${planName}`, async ({startIndent}) => {
return [
`when (${this.flavorSelector}) {`,
...flavors.map(flavor =>
ktUtils.indent(
`case "${flavor}": ${this.flavorClass(flavor)}.${planName}`,
)),
'}'
].join('\n');
}, {delegate: 'lazy'});
}
}
38 changes: 38 additions & 0 deletions src/tools/recipe2plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
*/
import {AllocatorRecipeResolver} from './allocator-recipe-resolver.js';
import {PlanGenerator} from './plan-generator.js';
import {FlavoredPlanGenerator} from './flavored-plan-generator.js';
import {assert} from '../platform/assert-node.js';
import {encodePlansToProto} from './manifest2proto.js';
import {Manifest} from '../runtime/manifest.js';
import {Dictionary} from '../utils/lib-utils.js';
import {Recipe} from '../runtime/recipe/lib-recipe.js';

export enum OutputFormat { Kotlin, Proto }

Expand Down Expand Up @@ -46,3 +49,38 @@ export async function recipe2plan(
default: throw new Error('Output Format should be Kotlin or Proto');
}
}


/**
* Generates Flavored Plans from recipes in an arcs manifest.
*
* @param path path/to/manifest.arcs
* @param format Kotlin or Proto supported.
* @param recipeFilter Optionally, target a single recipe within the manifest.
* @return Generated Kotlin code.
*/
export async function recipe2flavoredplan(
manifest: Manifest,
flavoredPolicies: Dictionary<Manifest>,
flavorSelector: string,
recipeFilter?: string,
salt = `salt_${Math.random()}`): Promise<string | Uint8Array> {

// TODO: If no policy or one policy is specified default to recipe2plan.
const flavoredPlans : Dictionary<Recipe[]> = {};
const resolvedRecipeNames: Set<string> = new Set();
for (const flavor of Object.keys(flavoredPolicies)) {
const policiesManifest = flavoredPolicies[flavor];
let plans = await (new AllocatorRecipeResolver(manifest, salt, policiesManifest)).resolve();
if (recipeFilter) {
plans = plans.filter(p => p.name === recipeFilter);
if (plans.length === 0) throw Error(`Recipe '${recipeFilter}' not found.`);
}
plans.forEach(recipe => resolvedRecipeNames.add(recipe.name));
flavoredPlans[flavor] = plans;
}

assert(manifest.meta.namespace, `Namespace is required in '${manifest.fileName}' for Kotlin code generation.`);
return new FlavoredPlanGenerator(
flavoredPlans, resolvedRecipeNames, manifest.meta.namespace, flavorSelector).generate();
}
Loading