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

'lerna bootstrap' fails to symlink binaries which are generated during package build #1444

Closed
rix0rrr opened this issue May 29, 2018 · 11 comments · Fixed by #2059
Closed

'lerna bootstrap' fails to symlink binaries which are generated during package build #1444

rix0rrr opened this issue May 29, 2018 · 11 comments · Fixed by #2059
Labels

Comments

@rix0rrr
Copy link
Contributor

rix0rrr commented May 29, 2018

We are using TypeScript. As part of the TypeScript "build", .js files are generated, some of which are command-line tools.

We are doing our TypeScript -> JavaScript compilation in the NPM "prepare" phase.

However, lerna bootstrap does not symlink the binaries that are generated as part of the prepare phase, because it executes lerna link once before running the prepare phases of all targets, and it makes no effort to link later.

From lerna-debug.log I can tell that it does execute preinstall for every package before symlinking, but the "preinstall" phase happens on every machine that installs the package, so is not the correct phase to do our TypeScript compilation in.

Reproduction:

Directory tree
├── lerna.json
├── package.json
└── packages
    ├── pkga
    │   └── package.json
    └── pkgb
        └── package.json

# pkga/package.json
{
  "name": "pkga",
  "version": "1.0.0",
  "main": "index.js",
  "bin": {
      "executable": "executable"
  },
  "scripts": {
      "prepare": "echo '#!/bin/bash' > executable && echo 'echo it runs' >> executable && chmod +x executable"
  }
}

# pkgb/package.json
{
  "name": "pkgb",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
      "prepare": "executable"
  },
  "devDependencies": {
      "pkga": "*"
  }
}

In short: executable is produced by pkga and used in the build of pkgb. This would have worked if pkga was downloaded from NPM, but does not work when the build is managed by Lerna.

Expected Behavior

For the build of package pkgb, it should not matter whether dependency pkga is obtained from NPM by npm install, or provided by lerna as part of a monorepo.

Current Behavior

Right now it does, because the symlinks to executables are evaluated before packages are event built, so binaries generated during the build are missed.

Possible Solution

A simple fix would be to run lerna link again after every package is bootstrapped. This would fix the issue, though it may be bad for performance. If you have enough information in the dependency tree to do selective updates that may be even better.

Steps to Reproduce (for bugs)

See the project layout given above.

$ lerna bootstrap
...
lerna ERR! execute Error occured with 'pkgb' while running 'npm run prepare'
{ Error: Command failed: npm run prepare
sh: 1: executable: not found

Proof that this is the issue, running lerna bootstrap again now that executable already exists during the link phase:

$ lerna bootstrap
...
lerna success Bootstrapped 2 packages

Note that running lerna bootstrap multiple times is not a solution, as we might not know in advance how many times we would need to run it! Depends on the amount of packages and the dependencies among them!

Context

We are writing packages in TypeScript. Some packages are build tools, containing command-line scripts that need to be run during the build of other packages.

We emphatically DO NOT want to run the build by hand and check in the generated .js files.

Your Environment

Executable Version
lerna --version 2.8.0
npm --version 6.1.0
node --version v8.9.1
OS Version
NAME VERSION
Ubuntu 16.04
rubensworks added a commit to comunica/comunica that referenced this issue Jun 1, 2018
This was because of our fix for installation during the monorepo.
This is an open problem of Lerna:
* lerna/lerna#385
* lerna/lerna#1444
@evocateur
Copy link
Member

Why not just have the binary files live as raw JS, importing a compiled "implementation" and calling it?

That being said, properly-expressed dependencies between the packages should elicit topologically-sorted execution of the lifecycle scripts during bootstrap. I have no experience with tsc's behaviour in this department, but I can guess it's doing a lexical iteration of package directories and failing due to this mismatch.

@rix0rrr
Copy link
Contributor Author

rix0rrr commented Jun 5, 2018

Why not just have the binary files live as raw JS, importing a compiled "implementation" and calling it?

Yeah, that's we end up doing to work around this, but I still feel lerna's behavior is wrong in this case.

As mentioned, if the upstream dependency was obtained through npm by using npm install, I could have used the binaries in my prepare script with no problems. However, when I build them at the same time using lerna, the same does not work. I was under the impression that lerna would be a replacement for a workflow where multiple packages together need to be manually npm installed or npm linked all the time.

I have no experience with tsc's behaviour in this department, but I can guess it's doing a lexical iteration of package directories and failing due to this mismatch.

No, every package is compiled individually (by doing npm run prepare) in the right topological order. The fact that source is being generated using tsc is incidental--note that my reproducing example had no mention of tsc in there anywhere.

The problem is that lerna is doing the link action at a time before any package has been built, so it fails to symlink the binaries that will be generated (obviously), and takes to effort to correct that later on.

@evocateur
Copy link
Member

evocateur commented Jun 5, 2018 via email

@jdolle
Copy link

jdolle commented Dec 10, 2018

I just encountered this bug this past week.
For others struggling, my temporary solution was to delete the node_modules and reinstall dependencies for the dependent package. And because I use yarn workspaces, deleting the node modules and re-yarning doesn't need to redownload anything.

"fixSymlink": "rm -rf ./node_modules && yarn",
"build": "yarn fixSymlink && someBin",

@evocateur
Copy link
Member

evocateur commented Dec 10, 2018 via email

@acao
Copy link

acao commented Dec 13, 2018

i switched my project from yarn to npm and now am experiencing this problem.
if you are having this issue with yarn, upgrade to the latest and it should resolve it. still a problem for npm however

@acao
Copy link

acao commented Dec 13, 2018

also, with yarn workspaces, i never needed to use lerna link or bootstrap

@stale
Copy link

stale bot commented Feb 11, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Feb 11, 2019
@stale stale bot closed this as completed Feb 18, 2019
@allanhortle
Copy link

I'm having this issue too. One of my packages is building a bin file during install which never gets symlinked into the sibling packages.

@stephenmathieson
Copy link

Also seeing this with Lerna version 3.4.3.

@rix0rrr
Copy link
Contributor Author

rix0rrr commented Apr 25, 2019

I actually think an easy way to resolve this is to create the symlinks in the downstream packages, based on what the { "scripts" } section promises will be the final executables, whether they exist yet or not.

evocateur pushed a commit that referenced this issue May 11, 2019
During the 'link' step, if a package's binary targets don't exist yet they will fail to be symlinked, even if a later `run xxx` step would have generated them. This is a situation that occurs in monorepos containing build tools compiled from another language (e.g. TypeScript).

Simply adding the symlinks whether the target exists or not is not good enough, as the symlink target needs to be `chmod +x`ed, which is lerna's/npm's responsibility.

Instead, if the target doesn't exist, generate a shim shell script that will `chmod` and `exec` the intended target, similar to what would happen on Windows normally.

Fixes #1444
lbwa referenced this issue in lbwa/esw Aug 25, 2021
We use the static js file to ensure that esw symlink alway work in the other packages when lerna boostraps.

Signed-off-by: Liu Bowen <mr_lbw@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants