Skip to content

Build a set of projects with a single command #11002

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

Open
filipesilva opened this issue May 24, 2018 · 39 comments
Open

Build a set of projects with a single command #11002

filipesilva opened this issue May 24, 2018 · 39 comments
Labels
area: @angular/cli feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Milestone

Comments

@filipesilva
Copy link
Contributor

It would be useful to have a way to build a certain set of projects on a workspace.

Perhaps this can be achieved with global workspace targets, or via a filtering mechanism.

Followup to #10397

@abhijeetkpawar
Copy link

Any plans for this feature?

@klemenoslaj
Copy link

klemenoslaj commented Feb 20, 2019

Hi @filipesilva,

Every project in a workspace already contains package.json (obviously). We could use peerDependencies to declare the dependencies between projects of the workspace. Then build and test commands would be able to detect the dependencies and build the libraries in the correct sequence.

I have written myself a node executable that does exactly that. Follows the dependency tree and builds the libraries in the correct sequence.

If that is something you see as a valuable addition to Angular CLI I would be more than happy to contribute.

@filipesilva
Copy link
Contributor Author

@klemenoslaj the approach we are looking at now would be able to do that implicitly, we hope. We're still finishing up the Architect API (this is what we use for build definitions).

@klemenoslaj
Copy link

@filipesilva, that sounds good 👍

I however still like the idea of using peerDependencies, because it forces people to think about their dependencies (very important for published packages) and if that is done correctly, build sequence is automatically correct.

I can see how that can be a bit complex for someone, but I still think handling dependencies via package.json is a good way to go.

@RobinBomkampDv
Copy link

Are there any plans when it can be expected?

@jpleclerc
Copy link

jpleclerc commented Apr 26, 2019

Is it possible with the new CLI builders announced here : https://blog.angular.io/introducing-cli-builders-d012d4489f1b ?

If yes could we have an example ?

@meriturva
Copy link

Actually, the builder approach is just project related and not workspace related. We already implemented npm script that just analyzes all projects, builds dependency tree and calls sequentially build but still problems during watch mode: build is too slow!

During development time I guess only productive way is to change tsconfig path as described on #10643 (comment)

@siddharth1903
Copy link

@meriturva Is this possible for multiple libraries within a single project? I understand schematics exposes options to invoke external schematics and builder is just an extension of schematics!?

@meriturva
Copy link

@Goonersid our approach is just for a single project with multiple libraries. Right now we have all to test new build based on bazel before improving our internal solution.

@siddharth1903
Copy link

@meriturva I'm looking for a cli cmd that builds all libraries within a single project... Is it possible with v6 or the latest stable? Something like ng build-all

@meriturva
Copy link

@Goonersid you have to write you own task script and run as: npm run build-all

@probert94
Copy link

probert94 commented Aug 28, 2019

@Goonersid I was also looking for something like this and created a script, which reads the angular.json file and executes ng build for every project and every configuration. Also this script collects all failed builds and prints them to stdout at the end.

EDIT:
I am using nrwl/nx, so I don't have to build libraries manually.
So this script will probably not work for default Angular CLI.

The script looks like this:

import { ProjectType, WorkspaceSchema } from "@schematics/angular/utility/workspace-models";
import { execSync } from "child_process";
import { readFileSync } from "fs";

interface ExecParams {
  project: string;
  config: string;
}

interface ExecError extends ExecParams {
  message: string;
}

function buildAll() {
  const json: WorkspaceSchema = JSON.parse(readFileSync("./angular.json").toString());
  const errors: ExecError[] = Object.keys(json.projects)
    // Filter application-projects
    .filter(name => json.projects[name].projectType === ProjectType.Application)
    // Determine parameters needed for the build command
    .reduce<ExecParams[]>((arr, project) => {
      const proj = json.projects[project];
      let build = proj.architect && proj.architect.build;
      if (build) {
        arr = arr.concat(...Object.keys(build.configurations || {})
          .map(config => ({ project, config }))
        );
      }
      return arr;
    }, [])
    // Execute `ng build` and collect errors.
    .reduce<ExecError[]>((err, exec) => {
      try {
        console.log(`Building ${exec.project} (${exec.config}):`);
        execSync(`ng build --prod --project ${exec.project} --configuration ${exec.config}`, {stdio: "inherit"});
      }
      catch (error) {
        err.push({
          project: exec.project,
          config: exec.config,
          message: error.message
        });
      }
      console.log("\n");
      return err;
    }, []);

  // Conditionally log errors
  if (errors.length === 0)
    console.log("Completed");
  else {
    console.log("\n");
    errors.forEach(error => {
      console.error(`Building ${error.project} (${error.config}) failed:\n\t${error.message}`);
    });
  }
}

buildAll();

This script can be compiled to Javascript and then you can run it using NodeJs.

@klemenoslaj
Copy link

@Springrbua but you're not following any dependencies, therefore if some library depends on another this will break or work by accident.

@probert94
Copy link

@klemenoslaj You are right, I never used the normal Angular CLI for multi app projects and therefore forgot, that you need to build libraries yourself.
I am using nrwl/nx, which automatically builds all needed libraries when executing ng build, so that's not a problem in my case.

@hiepxanh
Copy link
Contributor

I think this is an important feature for enterprise project with many library

@otiai10
Copy link

otiai10 commented Nov 13, 2020

I'm using too https://github.com/otiai10/too.js,
hoping it would help

@DerHerrGammler
Copy link

Any plans actually?

@HolzmannSpace
Copy link

HolzmannSpace commented Nov 27, 2020

A dirty hack (that ignores inner-dependencies) for windows would also be this BATCH-script:

DIR "projects\" /B /AD > "projects.txt"
FOR /f "usebackq delims=" %%f IN ("projects.txt") DO call ng build %%f
FOR /f "usebackq delims=" %%f IN ("projects.txt") DO call ng build %%f
DEL "projects.txt"

I do the FOR-loop twice because of an inner dependency in my projects. Also i use "call" to ignore errors in the first run.... dirty as hell but it's a workaround.

@krishnathota
Copy link

Any progress on this?

@hiepxanh
Copy link
Contributor

you can use https://nx.dev/latest/angular/generators/workspace-generators to build set of project, I'm using it to generate 6 project and 20 lib support for it will 1 command, easy

@klemenoslaj
Copy link

klemenoslaj commented May 22, 2021

As an exercise, I recently developed a little CLI tool that reads the dependencies from the TypeScript AST and executes the commands in the right sequence and parallel (if so specified).

I tried to use it on a quite large Angular CLI monorepo and the results were great. Dropping the sequential builds from 865s to 118s.

I did not publicly release this because a) It was an exercise b) Nx already does something like that.
If there is however a need for that I'd be glad to push it to the wild.

Bottom line is, there is a lot of potential in this so if the community requires this I'm sure the Angular team will implement something similar. I have a feeling tho that this is a bit exotic request so it might never happen.

Expand to see CLI in action videos
parallel-builds.mov
verbose-parallel-builds.mov
interactive.mov

@hiepxanh
Copy link
Contributor

hiepxanh commented May 22, 2021

interesting, that nice tool, I think you should share with people, it very good, your tool better than mine, I just using set of generator chain like this:

image

@krishnathota
Copy link

krishnathota commented May 24, 2021

@klemenoslaj

This is pretty good having various options to build. I think you should publish it out too if that's okay for you though.

I also do not understand why would this be an exotic request. As we could create just one repo instead on multiple for multiple libraries and there is a high chance that a library depends on an other one. Doesn't it?

Either I don't understand why or I am completely doing things wrong. 🤔

@klemenoslaj
Copy link

klemenoslaj commented May 24, 2021

This is pretty good having various options to build. I think you should publish it out too if that's okay for you though.

@krishnathota I probably will, but need to think if I can cope with the maintenance that comes along with it.

I also do not understand why would this be an exotic request

It is just my feeling/assumption, not the fact. I think the majority of applications out there are built without libraries or they go for Nx.
Again, that's just an assumption.

I'd be super happy seeying this in Angular CLI

@boeckMt
Copy link

boeckMt commented May 25, 2021

We had the need too, to build multiple libraries depending on their dependency tree, in order to publish them with GitHub Actions to NPM.

For this we used depcheck and toposort to generate the execution order (based on peerDependencies) and then used a small script to run all ng build commands.

But we also would be happy to see "workspace commands" for ng test <project-list> and ng build <project-list> inside Angular CLI :)

@klemenoslaj
Copy link

@boeckMt this is almost the same solution we used to have before. 😂 nice!

We moved to the CLI I showcased above because it got very slow. since all builds are sequential, so it sometimes took very long (big monorepo).

I think if Angular CLI builds that in, they'd need to account for parallel builds.

Glad to see there is someone else with similar issues 😆

@boeckMt
Copy link

boeckMt commented May 25, 2021

@klemenoslaj we only need this for for publishing our packages.
At "runtime" we use the default ng serve or ng build with a path mapping for the Imports https://github.com/dlr-eoc/ukis-frontend-libraries/blob/master/tsconfig.json#L21 to our package scope.

e.g.

import { xyz } from '@scope/package';
...
// tsconfig.json
...
"paths": {
      "@scope/*": [
        "dist/*",
        "projects/*"
      ],
      ...
    }
...

@klemenoslaj
Copy link

klemenoslaj commented May 25, 2021

We have this too 👍 We even generate it with out own schematics.
But in pipelines we build libraries anyway, because sometimes ng-packagr can trip over some syntax that would work in directly linked application build.


I wonder if there is any chance of getting this into Angular CLI. @filipesilva WDYT?

@klemenoslaj
Copy link

@klemenoslaj the approach we are looking at now would be able to do that implicitly, we hope. We're still finishing up the Architect API (this is what we use for build definitions).

There is not much going on in this feature request. I would still like to offer my repository for possible inspiration but will leave it at that due to lack of interest.

@filipesilva feel free to have a peek at the following repository. I implemented an implicit approach, so no additional config is required whatsoever.

The repository: https://github.com/klemenoslaj/ong-cli

@alan-agius4 alan-agius4 added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label Apr 1, 2022
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Apr 1, 2022
@vajahath
Copy link

vajahath commented May 5, 2022

This is indeed looks like a rough edge where the community seem to be struggling. I was able to stumble upon a lot of SO threads pointing to the need of this feature, for years.

To point out a few:

and all of these threads seem to be very genuine.

It would be very great when we run serve/build, cli would automatically build its depended libraries as well. The solution should also be compatible when ng serving multiple apps.

As of now what I'm doing is open a terminal per library and run ng build --watch. Guess how many terminal tabs I'm keeping!

@hiepxanh
Copy link
Contributor

hiepxanh commented May 5, 2022

I have to move nx to have this: https://nx.dev/generators/workspace-generators
I can build a set of project easy, example

import { appAndLibSetting } from '../../new-tool/common-setting.tool';
import { Tree, formatFiles, readProjectConfiguration } from '@nrwl/devkit';
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
import shellGenerator from '../shell';
import sharedGenerator from '../shared';

export default async function (tree: Tree, schema: any) {
  const domainName = `${schema.directory}-${schema.device}`;
  await createApplication(tree, schema);
  deleteUnecessaryFile(tree, schema, domainName);
  await createDependanceLibrary(tree, schema, domainName);

  await formatFiles(tree);
  return () => {};
}

function deleteUnecessaryFile(tree: Tree, schema: any, domainName: string) {
  const libraryRoot = readProjectConfiguration(tree, domainName).root;
  tree.delete(`${libraryRoot}/assets/.gitkeep`);
  tree.delete(`${libraryRoot}/environments/environment.prod.ts`);
  tree.delete(`${libraryRoot}/environments/environment.ts`);
}

async function createApplication(tree: Tree, schema: any) {
  const libraryGenerator = wrapAngularDevkitSchematic('@nrwl/angular', 'app');
  await libraryGenerator(tree, {
    ...schema,
    ...appAndLibSetting,
    name: schema.device,
    directory: schema.directory,
    routing: false,
    tags: `scope:shared,type:app`,
  });
}

async function createDependanceLibrary(tree: Tree, schema: any, domainName: string) {
  await shellGenerator(tree, {
    directory: `${schema.directory}/${schema.device}`,
  });
  await sharedGenerator(tree, {
    directory: `${schema.directory}/${schema.device}`,
  });
}


import { Tree, formatFiles, readProjectConfiguration, generateFiles, joinPathFragments, readJson } from '@nrwl/devkit';
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
import { classify, dasherize } from '@nrwl/workspace/src/utils/strings';
import { appAndLibSetting } from '../../new-tool/common-setting.tool';

export default async function (tree: Tree, schema: any) {
  await createShellLibrary(tree, schema, 'shell');
  const projectName = dasherize(`${schema.directory}-shell`).replace(/\//g, '-');
  addShellModule(tree, schema, projectName, 'shell');
  await formatFiles(tree);
  return () => {};
}

export async function createShellLibrary(tree: Tree, schema: any, type) {
  const libraryGenerator = wrapAngularDevkitSchematic('@nrwl/angular', 'lib');
  await libraryGenerator(tree, {
    name: `${schema.directory}/${type}`,
    tags: `scope:${type},scope:shared,type:${type}`,
    ...appAndLibSetting,
  });
}

export function addShellModule(tree: Tree, schema: any, projectName, type) {
  const libraryRoot = readProjectConfiguration(tree, projectName).root;
  generateFiles(tree, joinPathFragments(__dirname, `./files`), `${libraryRoot}/src/lib/${dasherize(`${schema.directory}-${type}`)}`, {
    ...schema,
    sharedModule: classify(`${schema.directory}-shared-module`.replace(/\//g, '-')),
    shellModule: classify(`${schema.directory}-shell-module`.replace(/\//g, '-')),
    shellName: dasherize(`${schema.directory}-shell`.replace(/\//g, '-')),
    sharedName: dasherize(`${schema.directory}-shared`.replace(/\//g, '-')),
  });
}

@meriturva
Copy link

Our way (without using nx) is to analyze all projects from angular.json file and create a dependency tree, then run multiple build commands based on tree (parallelly) to speed up the process.

See how we read angular.json file: https://medium.com/we-code/angular-inspect-workspace-programmatically-3cdfeb9b93d4

@CharlieStuart1995
Copy link

CharlieStuart1995 commented May 19, 2022

Our way (without using nx) is to analyze all projects from angular.json file and create a dependency tree, then run multiple build commands based on tree (parallelly) to speed up the process.

See how we read angular.json file: https://medium.com/we-code/angular-inspect-workspace-programmatically-3cdfeb9b93d4

Hi,

We are facing the same problem as you and you're solution sounds perfect! Would you be willing to share how you create the dependency tree and run build commands based on it?

Thanks in advance

@boeckMt
Copy link

boeckMt commented May 19, 2022

Our way (without using nx) is to analyze all projects from angular.json file and create a dependency tree, then run multiple build commands based on tree (parallelly) to speed up the process.
See how we read angular.json file: https://medium.com/we-code/angular-inspect-workspace-programmatically-3cdfeb9b93d4

Hi,

We are facing the same problem as you and you're solution sounds perfect! Would you be willing to share how you create the dependancy tree and run build commands based on it?

Thanks in advance

@meriturva I would also be interested in how you did this because it sounds very similar to what we did :)

@K3CharlieStuart current solutions I know now are:

@CharlieStuart1995
Copy link

@boeckMt thanks for the suggestions!

I'm just looking into your second suggestion but I cant seem to figure out how we would utilise this in our project. Would it be a case of copying the script folder into our application and somehow executing the quoted function?

@meriturva
Copy link

Here is just a code snippet of how we build our dependencies tree, it is really simple,
the complex one is to run all scripts parallel and that script it is on our private repo right now and I cannot share it for now.
May be I could arrange an article (or a few) about that. Would you be interested?

export function buildPeerDependenciesTree(filterArchitect: string): Record<string, PackageNode> {

  const angularJson = JSON.parse(fs.readFileSync('angular.json', 'utf8'));

  const nodes: Record<string, PackageNode> = {}; 

  Object.values(angularJson.projects).forEach((value) => {
    if ((value as any).projectType === 'library' && (value as any).architect[filterArchitect]) {
      const packageContent = JSON.parse(fs.readFileSync(path.join((value as any).root, 'package.json'), 'utf8'));
      const moduleName = packageContent.name;
      const peerDependencies = new Set<string>();

      // Look for peerDependencies also in package.json in subFolders
      const allPackageJson = glob.sync(`${(value as any).root}/**/package.json`);
      allPackageJson.forEach(filePath => {
        const fileContent = JSON.parse(fs.readFileSync(filePath, 'utf8'));
        if (fileContent && fileContent.peerDependencies) {
          Object.keys(fileContent?.peerDependencies).forEach(peerDependency => peerDependencies.add(peerDependency));
        }
      });

      if (!nodes[moduleName]) {
        nodes[moduleName] = new PackageNode(moduleName, (value as any).root, (value as any).sourceRoot);
      }

      nodes[moduleName].package = packageContent;

      peerDependencies.forEach((nodeDependency) => {
        // Check if dependency is present locally (to be compile)
        if (angularJson.projects[nodeDependency]?.architect[filterArchitect]) {
          if (!nodes[nodeDependency]) {
            nodes[nodeDependency] = new PackageNode(nodeDependency, (angularJson.projects[nodeDependency] as any).root, (angularJson.projects[nodeDependency] as any).sourceRoot);
          }

          nodes[moduleName].originalDependencies.push(nodeDependency);
          nodes[moduleName].dependencies.push(nodeDependency);
        }
      });
    }
  });

  return nodes;
}

@klemenoslaj
Copy link

klemenoslaj commented May 19, 2022

@boeckMt thanks for the suggestions!

I'm just looking into your second suggestion but I cant seem to figure out how we would utilise this in our project. Would it be a case of copying the script folder into our application and somehow executing the quoted function?

Sorry for repeating the same thing, but I feel we are going back to basics here - reading angular.json etc.

Have a look at the #11002 (comment) where I made an entire repo public - it even supports parallel builds no matter the complexity of dependency tree.

Running example in the following comment: #11002 (comment)

Due to lack of interest I did not yet made this an npm package tho, but feel free to consume & contribute.

@PrestonMonteWest
Copy link

Definitely interested in something like this. It would be nice to specify what projects to build and the order to build them. I know it can be done manually through scripts in the package.json, but doing so is cumbersome and inconsistent with the highly configurable nature of the build process in general. Also, a first-party solution from Angular would help streamline and standardize the process. It looks like this isn't being pursued at the moment in a first-party solution, so I'm adding my comment here to give this more support.

@Volvohorst
Copy link

I would be highly interested in a "native" Angular CLI solution as well.
It would greatly improve our ability to guarantee development quality and provide a consistent dev environment to our developers.
Providing hints at https://angular.dev/tools/libraries would have been nice too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: @angular/cli feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Projects
None yet
Development

No branches or pull requests