Skip to content

Commit

Permalink
wip: .
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl committed Jan 22, 2019
1 parent db3c306 commit 811b596
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 109 deletions.
11 changes: 6 additions & 5 deletions packages/angular_devkit/architect/builders/and.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { json } from "@angular-devkit/core";
import { of, from, EMPTY } from "rxjs";
import { concatMap, map, mergeMap } from "rxjs/operators";
import { Schema as OperatorSchema } from './operator-schema';
import { json } from '@angular-devkit/core';
import { EMPTY, from, of } from 'rxjs';
import { concatMap, map, mergeMap } from 'rxjs/operators';
import { BuilderOutput, BuilderRun, createBuilder } from '../src/api';
import { Schema as OperatorSchema } from './operator-schema';

export default createBuilder<json.JsonObject & OperatorSchema>((options, context) => {
let allRuns: Promise<[number, BuilderRun]>[] | null = null;

if (options.targets) {
allRuns = options.targets.map((targetStr, i) => {
const [project, target, configuration] = targetStr.split(/:/g, 3);
return context.scheduleTarget(project, target, configuration, {})

return context.scheduleTarget({ project, target, configuration }, {})
.then(run => [i, run] as [number, BuilderRun]);
});
} else if (options.builders && options.options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { experimental } from '@angular-devkit/core';
import { experimental, json } from '@angular-devkit/core';
import { resolve } from '@angular-devkit/core/node';
import * as path from 'path';
import { ArchitectHost, BuilderInfo } from '../src/api';
Expand Down Expand Up @@ -80,4 +80,16 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost {
getWorkspaceRoot(): string {
return this._root;
}

getOptionsForTarget(target: Target): Promise<json.JsonObject> {
const targetSpec = this._workspace.getProjectTargets(target.project)[target.target];
if (target.configuration && !targetSpec['configurations']) {
throw new Error('Configuration not set in the workspace.');
}

return {
...targetSpec['options'],
...(target.configuration ? targetSpec['configurations'][target.configuration] : 0),
};
}
}
196 changes: 130 additions & 66 deletions packages/angular_devkit/architect/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { experimental, isPromise, json, logging, normalize } from '@angular-devkit/core';
import { experimental, isPromise, json, logging } from '@angular-devkit/core';
import { Observable, from, isObservable, of } from 'rxjs';
import { concatMap, first, map, switchMap, tap } from 'rxjs/operators';
import { concatMap, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { JobInboundMessageKind, JobOutboundMessageKind } from '../../core/src/experimental/jobs';
import { Schema as RealBuilderInput, Target } from './input-schema';
import { Schema as RealBuilderOutput } from './output-schema';

import { Schema as RealBuilderProgress } from './progress-schema';

export type Target = Target;

Expand All @@ -29,56 +29,50 @@ export type BuilderRegistry<

export type BuilderInput = json.JsonObject & RealBuilderInput;
export type BuilderOutput = json.JsonObject & RealBuilderOutput;

export type BuilderProgress = json.JsonObject & RealBuilderProgress;

const inputSchema = require('./input-schema.json');
const outputSchema = require('./output-schema.json');
const progressSchema = require('./progress-schema.json');



export enum BuilderState {
Stopped = 'stopped',
Waiting = 'waiting',
Building = 'building',
Error = 'error',
}

export interface BuilderRun {
builderName: string;

// This always replay the last output.
output: Observable<BuilderOutput>;
// This always replay the last state.
state: Observable<BuilderState>;

//

// This will always replay the last progress on new subscriptions.
progress: Observable<BuilderProgress>;
}

export interface BuilderDescription extends experimental.jobs.JobDescription {
info: BuilderInfo;
}

export const BuilderSymbol = Symbol.for('@angular-devkit/architect:builder');
export const VersionSymbol = Symbol.for('@angular-devkit/architect:version');


export interface Builder<OptionT extends json.JsonObject> {
handler: experimental.jobs.JobHandler<boolean, BuilderInput, BuilderOutput>;
'@@Architect_Builder': true;
[BuilderSymbol]: true;
[VersionSymbol]: string;
}

export interface BuilderContext {
logger: logging.LoggerApi;

projectRoot: string;
workspaceRoot: string;
currentDirectory: string;

// Target is optional if a builder was ran by name.
target?: Target;

// scheduleJob()
scheduleTarget(
project: string,
target: string,
configuration: string | undefined,
options: json.JsonObject,
target: Target,
overrides: json.JsonObject,
): Promise<BuilderRun>;
// scheduleBuilder()

Expand Down Expand Up @@ -126,6 +120,62 @@ export interface ArchitectHost {

getCurrentDirectory(): string;
getWorkspaceRoot(): string;

getOptionsForTarget(target: Target): Promise<json.JsonObject>;
}

/**
* Returns a string of "project:target[:configuration]" for the target object.
*/
export function targetToTargetString({project, target, configuration}: Target) {
return `${project}:${target}${configuration !== undefined ? ':' + configuration : ''}`;
}

async function _scheduleTarget(
target: Target,
overrides: json.JsonObject,
scheduler: experimental.jobs.Scheduler,
options: {
logger: logging.Logger,
workspaceRoot: string,
currentDirectory: string,
},
): Promise<BuilderRun> {
const name = `{${targetToTargetString(target)}}`;
const job = scheduler.schedule<{}, BuilderInput, BuilderOutput>(name, {});

// Wait for the job to be ready.
if (job.state !== experimental.jobs.JobState.Started) {
job.outboundBus.subscribe(event => {
if (event.kind === experimental.jobs.JobOutboundMessageKind.Start) {
job.input.next({
currentDirectory: options.workspaceRoot,
workspaceRoot: options.currentDirectory,
reason: 'initial',
options: overrides,
target,
} as BuilderInput);
}
});
} else {

}

job.outboundBus.subscribe(
message => {
if (message.kind == experimental.jobs.JobOutboundMessageKind.Log) {
options.logger.next(message.entry);
}
},
);

const description = await job.description.toPromise();

return {
builderName: description.name,
output: job.output.pipe(shareReplay(1)),
progress: new Observable<BuilderProgress>(),
};
}

export function createBuilder<OptT extends json.JsonObject>(
Expand All @@ -136,11 +186,7 @@ export function createBuilder<OptT extends json.JsonObject>(
context: experimental.jobs.JobHandlerContext<boolean, BuilderInput, BuilderOutput>,
) {
const description = context.description;

function scheduleTarget(project: string, target: string, configuration: string | undefined, options: json.JsonObject): Promise<BuilderRun> {
const name = `{${project}:${target}${configuration ? ':' + configuration : ''}}`;
const job = context.scheduler.schedule(name, options);
}
const scheduler = context.scheduler;

return new Observable<experimental.jobs.JobOutboundMessage<BuilderOutput>>(observer => {
const logger = new logging.Logger('job');
Expand All @@ -156,11 +202,17 @@ export function createBuilder<OptT extends json.JsonObject>(
if (x.kind === experimental.jobs.JobInboundMessageKind.Input) {
const i = x.value;
const context: BuilderContext = {
projectRoot: normalize(i.workspaceRoot),
currentDirectory: normalize(i.currentDirectory),
workspaceRoot: i.workspaceRoot,
currentDirectory: i.currentDirectory,
target: i.target,
logger,
scheduleTarget,
scheduleTarget(target: Target, overrides: json.JsonObject) {
return _scheduleTarget(target, overrides, scheduler, {
logger,
workspaceRoot: i.workspaceRoot,
currentDirectory: i.currentDirectory,
});
},
};

let result = fn(i.options as OptT, context);
Expand Down Expand Up @@ -193,20 +245,27 @@ export function createBuilder<OptT extends json.JsonObject>(

return {
handler: Object.assign(handler, { jobDescription: {} }),
'@@Architect_Builder': true,
[BuilderSymbol]: true,
[VersionSymbol]: require('../package.json').version,
};
}

function _createBackwardCompatibleJobHandlerFromBuilderInfo(
info: BuilderInfo,
target: Target | undefined,
registry: json.schema.SchemaRegistry,
baseOptions: json.JsonObject,
): Observable<BuilderJobHandler | null> {
// This will only work in Node, but it's not illegal in a browser (as long as it's not
// executed).
return from(import('./backward-compatible')).pipe(
switchMap(module => {
return from(module.createBackwardCompatibleJobHandlerFromBuilderInfo(info, target, registry));
return from(module.createBackwardCompatibleJobHandlerFromBuilderInfo(
info,
target,
registry,
baseOptions,
));
}),
);
}
Expand All @@ -215,6 +274,7 @@ function _createJobHandlerFromBuilderInfo(
info: BuilderInfo,
_target: Target | undefined,
registry: json.schema.SchemaRegistry,
baseOptions: json.JsonObject,
): Observable<BuilderJobHandler> {
const jobDescription: BuilderDescription = {
name: info.name,
Expand All @@ -226,7 +286,7 @@ function _createJobHandlerFromBuilderInfo(

const loader = async () => {
const builder = (await import(info.import)).default;
if (builder['@@Architect_Builder']) {
if (builder[BuilderSymbol]) {
return builder.handler;
}

Expand All @@ -238,7 +298,10 @@ function _createJobHandlerFromBuilderInfo(
concatMap(message => {
if (message.kind === JobInboundMessageKind.Input) {
const v = message.value as BuilderInput;
const options = v.options || {};
const options = {
...baseOptions,
...v.options,
};

// Validate v.options
return registry.compile(info.optionSchema).pipe(
Expand Down Expand Up @@ -298,17 +361,21 @@ export class ArchitectBuilderJobRegistry implements BuilderRegistry {
return from(this._host.resolveBuilder(name));
}

protected _createBuilder(info: BuilderInfo, target?: Target) {
protected _createBuilder(info: BuilderInfo, target?: Target, options?: json.JsonObject) {
const cache = this._cache;
if (cache && cache.has(info.name)) {
return of(cache.get(info.name) || null);
}

let result: Observable<BuilderJobHandler | null>;
if (info.version == 1) {
result = _createBackwardCompatibleJobHandlerFromBuilderInfo(info, target, this._registry);
result = _createBackwardCompatibleJobHandlerFromBuilderInfo(
info,
target,
this._registry,
options || {});
} else {
result = _createJobHandlerFromBuilderInfo(info, target, this._registry);
result = _createJobHandlerFromBuilderInfo(info, target, this._registry, options || {});
}

if (cache) {
Expand Down Expand Up @@ -352,9 +419,21 @@ export class ArchitectTargetJobRegistry extends ArchitectBuilderJobRegistry {
configuration: m[3],
};

return from(this._host.getBuilderNameForTarget(target)).pipe(
concatMap(builderStr => this._resolveBuilder(builderStr)),
concatMap(builderInfo => builderInfo ? this._createBuilder(builderInfo, target) : of(null)),
return from(Promise.all([
this._host.getBuilderNameForTarget(target),
this._host.getOptionsForTarget(target),
])).pipe(
concatMap(([builderStr, options]) => {
return this._resolveBuilder(builderStr).pipe(
concatMap(builderInfo => {
if (builderInfo === null) {
return of(null);
}

return this._createBuilder(builderInfo, target, options);
}),
);
}),
first(null, null),
) as Observable<experimental.jobs.JobHandler<A, I, O> | null>;
}
Expand Down Expand Up @@ -392,32 +471,17 @@ export class Architect {
return this._scheduler.has(name);
}

scheduleTarget<A extends json.JsonObject>(
project: string,
target: string,
configuration: string | undefined,
options: A,
): experimental.jobs.Job<{}, BuilderInput, BuilderOutput> {
const name = `{${project}:${target}${configuration === undefined ? '' : ':' + configuration}}`;
const job = this._scheduler.schedule(name, {});

// Wait for the job to be ready.
job.outboundBus.subscribe(event => {
if (event.kind === experimental.jobs.JobOutboundMessageKind.Start) {
job.input.next({
currentDirectory: this._host.getCurrentDirectory(),
workspaceRoot: this._host.getWorkspaceRoot(),
reason: 'initial',
options,
target: {
project,
target,
configuration,
},
} as BuilderInput);
}
scheduleTarget(
target: Target,
overrides: json.JsonObject,
options: {
logger?: logging.Logger,
} = {},
): Promise<BuilderRun> {
return _scheduleTarget(target, overrides, this._scheduler, {
logger: options.logger || new logging.NullLogger(),
currentDirectory: this._host.getCurrentDirectory(),
workspaceRoot: this._host.getWorkspaceRoot(),
});

return job;
}
}
Loading

0 comments on commit 811b596

Please sign in to comment.