Skip to content

Commit

Permalink
feat: standard build (#1201)
Browse files Browse the repository at this point in the history
Define a common build process for all projects which includes a set of standard phases that can be used for extensibility. The idea is that components and project types will have a solid foundation for build extensions instead of ad-hoc set of tasks for each project.

The `build` task now spawns a set of standard build phase tasks:
- `default` (synthesize project - executes your projenrc).
- `pre-compile`
- `compile`
- `post-compile`
- `test`
- `package`

The `Project` base class now has a set of properties that can be used to access these tasks. For example, `project.postcompileTask` will return the `Task` that’s executed after compilation.

The method `task.lock()` can now be used to lock a task for mutations. The `build` task is now locked, which means that any modifications to it will throw an exception.

This change also includes some logging improvements and cleanups.

This is an attempt to create generic build model for all project types. We shall see if this holds water.

BREAKING CHANGE: It is now impossible to modify the `build` task. Instead, extend one of the specific build phases (`precompile`, `compile`, `post compile`, `test` and `package`). To access these tasks use `project.xxxTask`.
* The `compileBeforeTest` option in `TypeScriptProject` is not supported any more. Tests are always executed _after_ compilation.
* `projenDuringBuild` is no longer supported. Let us know if you have a use case for it that we are not aware of.

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • Loading branch information
Elad Ben-Israel committed Nov 7, 2021
1 parent 1378684 commit 2b38918
Show file tree
Hide file tree
Showing 66 changed files with 2,117 additions and 935 deletions.
33 changes: 25 additions & 8 deletions .projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const macros = project.addTask('readme-macros');
macros.exec('mv README.md README.md.bak');
macros.exec('cat README.md.bak | markmac > README.md');
macros.exec('rm README.md.bak');
project.buildTask.spawn(macros);
project.postCompileTask.spawn(macros);

new JsonFile(project, '.markdownlint.json', {
obj: {
Expand Down
216 changes: 92 additions & 124 deletions API.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions docs/project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Projects

This section describes common behaviors for all projen projects. It is
implemented as part of the `Project` base type, from which all projen projects
are derived.

## Build Tasks

Projen defines a standard way for building software through a fixed set of
*build phases*. This is implemented via a set of [tasks](./tasks.md) defined in
the `Project` base class.

The `build` task spawns a set of sub-tasks which represent the various build phases:

* `default` - this task is responsible to execute your projenrc and synthesize all project files.
* `pre-compile` - runs before compilation (eg. bundle assets)
* `compile` - compile your code (if needed)
* `post-compile` - runs immediately after a successful compilation
* `test` - runs tests
* `package` - creates a distribution package

To extend the build process, components and projects can use
`project.projectBuild.xxxTask` and interact with the `Task` object (i.e.
`project.projectBuild.postCompileTask.exec("echo hi")` will execute `echo hi` after
compilation).

> NOTE: the `build` task is locked. This means that any attempt to extend it
> (i.e. call `spawn`, `exec`, `reset`, etc) will throw an exception. Instead of
> extending `build`, just extend one of the phases. This ensures that phases are
> always executed in the right order.
2 changes: 2 additions & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/awscdk-app-ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ export class AwsCdkTypeScriptApp extends TypeScriptAppProject {
this.removeScript('watch'); // because we use ts-node

// add synth to the build
this.buildTask.spawn(this.cdkTasks.synth);

this.postCompileTask.spawn(this.cdkTasks.synth);

this.cdkConfig = new CdkConfig(this, {
app: `npx ts-node --prefer-ts-exts ${path.posix.join(this.srcdir, this.appEntrypoint)}`,
Expand Down
2 changes: 1 addition & 1 deletion src/awscdk/java-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class AwsCdkJavaApp extends JavaProject {
this.addCdkDependency(...options.cdkDependencies ?? []);

this.cdkTasks = new CdkTasks(this);
this.buildTask.spawn(this.cdkTasks.synth);
this.postCompileTask.spawn(this.cdkTasks.synth);

this.cdkConfig = new CdkConfig(this, {
app: `mvn exec:java --quiet -Dexec.mainClass=${this.mainClass}`,
Expand Down
4 changes: 2 additions & 2 deletions src/awscdk/lambda-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ export class LambdaFunction extends Component {
externals: options.externals ?? ['aws-sdk'],
});

this.project.logger.info(`${basePath}: construct "${constructName}" generated under "${constructFilePath}"`);
this.project.logger.info(`${basePath}: bundle task "${this.bundleTask.name}"`);
this.project.logger.verbose(`${basePath}: construct "${constructName}" generated under "${constructFilePath}"`);
this.project.logger.verbose(`${basePath}: bundle task "${this.bundleTask.name}"`);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/cdk8s-app-ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,11 @@ export class Cdk8sTypeScriptApp extends TypeScriptAppProject {
this.gitignore.include('cdk8s.yaml');

// add synth to the build
this.buildTask.spawn(synth);
this.postCompileTask.spawn(synth);

if (options.sampleCode ?? true) {
new SampleCode(this);
}

}

}
Expand Down
8 changes: 6 additions & 2 deletions src/cli/synth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export async function synth(runtime: TaskRuntime, options: SynthOptions) {

throw new Error('Unable to find a task named "default"');
} catch (e) {
logging.error(`Synthesis failed: ${e.message}`);
logging.error(`Synthesis failed: ${(e as any).message}`);
return false;
}
}
Expand Down Expand Up @@ -119,7 +119,11 @@ export async function synth(runtime: TaskRuntime, options: SynthOptions) {
fs.symlinkSync(projenModule, projenModulePath, (os.platform() === 'win32') ? 'junction' : null);
}

spawnSync(process.execPath, [rcfile], { stdio: 'inherit' });
const ret = spawnSync(process.execPath, [rcfile], { stdio: ['inherit', 'inherit', 'pipe'] });
if (ret.status !== 0) {
throw new Error(`Synthesis failed: ${ret.error}`);
}

return true;
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ export const PROJEN_RC = '.projenrc.js';
export const PROJEN_DIR = '.projen';
export const PROJEN_MARKER = '~~ Generated by ' + 'projen'; // we split into two so /this/ file does not match the marker

export const IS_TEST_RUN = (process.env.NODE_ENV === 'test');

// eslint-disable-next-line @typescript-eslint/no-require-imports
const packageVersion = require('../package.json').version;

// if we are running with a test, report a fake stable version
export const PROJEN_VERSION = (process.env.NODE_ENV === 'test')
export const PROJEN_VERSION = IS_TEST_RUN
? '99.99.99'
: packageVersion;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './node-project';
export * from './object-file';
export * from './option-hints';
export * from './project';
export * from './project-build';
export * from './projects';
export * from './readme';
export * from './sample-file';
Expand Down
10 changes: 0 additions & 10 deletions src/java/java-project.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GitHubProject, GitHubProjectOptions } from '../project';
import { Task } from '../tasks';
import { Junit, JunitOptions } from './junit';
import { MavenCompile, MavenCompileOptions } from './maven-compile';
import { MavenPackaging, MavenPackagingOptions } from './maven-packaging';
Expand Down Expand Up @@ -139,11 +138,6 @@ export class JavaProject extends GitHubProject {
*/
public readonly distdir: string;

/**
* The primary build task.
*/
public readonly buildTask: Task;

constructor(options: JavaProjectOptions) {
super(options);

Expand Down Expand Up @@ -187,10 +181,6 @@ export class JavaProject extends GitHubProject {
},
});

const buildTask = this.addTask('build', { description: 'Full CI build' });
buildTask.spawn(this.packaging.task);
this.buildTask = buildTask;

for (const dep of options.deps ?? []) {
this.addDependency(dep);
}
Expand Down
5 changes: 1 addition & 4 deletions src/java/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ export class Junit extends Component {
pom.addTestDependency(`org.junit.jupiter/junit-jupiter-api@${version}`);
pom.addTestDependency(`org.junit.jupiter/junit-jupiter-engine@${version}`);

project.addTask('test', {
description: 'Runs tests',
exec: 'mvn test',
});
project.testTask.exec('mvn test');

const javaPackage = options.sampleJavaPackage ?? 'org.acme';
const javaPackagePath = javaPackage.split('.');
Expand Down
8 changes: 1 addition & 7 deletions src/java/maven-compile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component } from '../component';
import { Project } from '../project';
import { Task } from '../tasks';
import { Pom } from './pom';

/**
Expand All @@ -26,8 +25,6 @@ export interface MavenCompileOptions {
* Adds the maven-compiler plugin to a POM file and the `compile` task.
*/
export class MavenCompile extends Component {
public readonly compileTask: Task;

constructor(project: Project, pom: Pom, options: MavenCompileOptions = {}) {
super(project);

Expand All @@ -40,9 +37,6 @@ export class MavenCompile extends Component {
},
});

this.compileTask = project.addTask('compile', {
description: 'Compile the main source files',
exec: 'mvn compiler:compile',
});
project.compileTask.exec('mvn compiler:compile');
}
}
18 changes: 6 additions & 12 deletions src/java/maven-packaging.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component } from '../component';
import { Project } from '../project';
import { Task } from '../tasks';
import { Pom } from './pom';

/**
Expand Down Expand Up @@ -36,11 +35,6 @@ export interface MavenPackagingOptions {
* Configures a maven project to produce a .jar archive with sources and javadocs.
*/
export class MavenPackaging extends Component {
/**
* The "package" task.
*/
public readonly task: Task;

constructor(project: Project, pom: Pom, options: MavenPackagingOptions = {}) {
super(project);

Expand Down Expand Up @@ -89,12 +83,12 @@ export class MavenPackaging extends Component {
};

const distdir = options.distdir ?? 'dist/java';
this.task = project.addTask('package', {
description: `Creates a java deployment package under ${distdir}`,
env,
});
this.task.exec(`mkdir -p ${distdir}`);
this.task.exec(`mvn deploy -D=altDeploymentRepository=local::default::file:///$PWD/${distdir}`);

for (const [k, v] of Object.entries(env)) {
this.project.packageTask.env(k, v);
}
this.project.packageTask.exec(`mkdir -p ${distdir}`);
this.project.packageTask.exec(`mvn deploy -D=altDeploymentRepository=local::default::file:///$PWD/${distdir}`);

project.gitignore.exclude(distdir);
}
Expand Down
5 changes: 2 additions & 3 deletions src/java/projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@ export class Projenrc extends Component {
pom.addPlugin('org.codehaus.mojo/exec-maven-plugin@3.0.0');

// set up the "default" task which is the task executed when `projen` is executed for this project.
const defaultTask = project.addTask(Project.DEFAULT_TASK, { description: 'Synthesize the project' });
defaultTask.exec(`mvn ${compileGoal} --quiet`);
defaultTask.exec(`mvn exec:java --quiet -Dexec.mainClass=${this.className}${execOpts}`);
project.defaultTask.exec(`mvn ${compileGoal} --quiet`);
project.defaultTask.exec(`mvn exec:java --quiet -Dexec.mainClass=${this.className}${execOpts}`);

// if this is a new project, generate a skeleton for projenrc.java
this.generateProjenrc();
Expand Down
11 changes: 3 additions & 8 deletions src/javascript/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ export interface BundlerCommonOptions {
* Options for `Bundler`.
*/
export interface BundlerOptions extends BundlerCommonOptions {
/**
* A parent task that will spawn the "bundle" task (usually "compile").
*/
readonly parentTask: Task;

}

/**
Expand All @@ -42,18 +39,16 @@ export class Bundler extends Component {
}

private _task: Task | undefined;
private readonly parentTask: Task;

public readonly esbuildVersion: string | undefined;

/**
* Creates a `Bundler`.
*/
constructor(project: Project, options: BundlerOptions) {
constructor(project: Project, options: BundlerOptions = {}) {
super(project);

this.esbuildVersion = options.esbuildVersion;
this.parentTask = options.parentTask;
}

/**
Expand All @@ -68,7 +63,7 @@ export class Bundler extends Component {
description: 'Bundle assets',
});

this.parentTask.spawn(this._task);
this.project.preCompileTask.spawn(this._task);
}

return this._task;
Expand Down
3 changes: 1 addition & 2 deletions src/javascript/projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { existsSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { Component } from '../component';
import { renderJavaScriptOptions } from '../javascript/render-options';
import { NodeProject } from '../node-project';
import { Project } from '../project';
export interface ProjenrcOptions {
/**
Expand All @@ -24,7 +23,7 @@ export class Projenrc extends Component {
this.rcfile = options.filename ?? '.projenrc.js';

// this is the task projen executes when running `projen`
project.addTask(NodeProject.DEFAULT_TASK, { exec: `node ${this.rcfile}` });
project.defaultTask.exec(`node ${this.rcfile}`);

this.generateProjenrc();
}
Expand Down
3 changes: 2 additions & 1 deletion src/jsii-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export class JsiiDocgen {
exec: 'jsii-docgen',
});

project.buildTask.spawn(docgen);
// spawn docgen after compilation (requires the .jsii manifest).
project.postCompileTask.spawn(docgen);
project.gitignore.include('/API.md');
}
}

0 comments on commit 2b38918

Please sign in to comment.