Skip to content

Conversation

@barlock
Copy link

@barlock barlock commented May 18, 2023

This PR automates and builds prebuilt binaries that get downloaded as a post-install step instead of building.

Typically builds on CI take me 6 minutes which over time adds up pretty significantly (especially when you consider that adding up to about 4 years of total community compute time per week of installs of this package 😱). This also cuts the build requirements for windows machines.

I've been using this branch in a fork to create prebuilds for myself since Dec and it's been working great.

The only change to typical process that you would need to make after this change would be that instead of creating just tags, you would need to create a release with the same name as the tag. You can see examples in my fork.

Copy link
Member

@jmillan jmillan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really nice!

npm-scripts.js Outdated
function extractRemoteTar(tarUrl, cwd)
{
const tar = require('tar');
const axios = require('axios');
Copy link
Member

@jmillan jmillan May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid this dependency just for making a single HTTP request?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unaware of a simple, dependency free way to make http requests. fetch in node is experimental in 17.5 and still hasn't entered stability in 19.

Do you know of a way? (Simple being the key, I can use node:http but it would add a lot of code)

Copy link
Member

@jmillan jmillan May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node-fetch is 107KB while axios is 1.74MB.

I think the former is more than enough for the needed usage.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Swapped for node-fetch. Note that node-fetch >= 3 requires ESM, so I used node-fetch@2

@ibc
Copy link
Member

ibc commented May 19, 2023

Actually we already rejected a similar PR in the past and I would like to re-check the reasons why we did it. Let's please put this PR on hold for a while until we check that.

@barlock
Copy link
Author

barlock commented May 19, 2023

Previously you'd rejected issues I'd created b/c you didn't want to maintain building the binaries and thought letting someone else create the binaries for you would be easier, which it obviously is. I think this introduces security risks though (Eg, how does an installer know that the binaries in the npm package aren't malicious if they don't come from the authors of the project?).

Since the previous recommendation was "Put it in a separate page" I did that, as it's been working for a while, I figured I'd at least make the PR and see if your mind had changed, otherwise I'll keep maintaining my fork with this branch while I'm using this project.

Co-authored-by: José Luis Millán <jmillan@aliax.net>
@ibc
Copy link
Member

ibc commented May 19, 2023

I see, thanks. Changes make sense and look great. Let's please have some days before we fully review and merge this PR.

@ibc
Copy link
Member

ibc commented May 19, 2023

Easy question for now: how to prevent "npm ci" in my mediasoup local repo from downloading the binary instead of locally building it? If you call "make clean" in worker folder in your local fork and then run "npm run release:check", does it build the worker or does it fetch it from GH? It must do the former.

@barlock
Copy link
Author

barlock commented May 19, 2023

npm run release:check will build the worker.

npm ci without any flags will download it, however npm run release:check specifically adds --ignore-scripts so it can explicitly build it without using postinstall

.gitignore Outdated

## Worker.
/worker/out
out/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? Why was worker/out worse? INMHO it makes sense that worker binary is located in worker/ folder. Do I miss something?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed a place to download the prebuilds to, I wanted them to be different than worker/out so we wouldn't accidentally start taring tarballs, so I put them in repo-root/out. the binary is still in worker/out.

repo-root/out is fairly arbitrary, I was guessing what you'd prefer based on patterns in the repo, do you have thoughts on a different location for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love worker/prebuilds/ and of course gitignore it.

{
buildWorker();

if (!process.env.MEDIASOUP_LOCAL_DEV)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not remove this condition. If MEDIASOUP_LOCAL_DEV env is given we should neither download binary from GH nor building it locally. Here in this PR we are downloading the binary from GH anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to update the PR to do what you described in this comment, as the logic is simpler and the result should be faster. (If MEDIASOUP_LOCAL_DEV clean worker, else, attempt to download, if that fails, build it.

However, I think the current implementation if MEDIASOUP_LOCAL_DEV is set, builds the worker then removes it? Is that intentional?

Let me know if I misinterpreted your feedback.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If MEDIASOUP_LOCAL_DEV is given then it should not download the prebuilt binary, it should build it locally and should NOT delete built artifacts after it. It's basically for local development for not having to build everything when running make in worker folder.

However my comment was wrong (the above is correct). I meant another variable that is... I don't remember but you can see it in Worker.ts. If given, install or postinstall script should neither download binary nor try to build local one. This is for people that want to run mediasoup with a binary compiled by themselves in some path (given in that env). Hope it's clear now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I meant MEDIASOUP_WORKER_BIN which is used in lines below.

npm-scripts.js Outdated
}
}

function createTar(files, dest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this function be async? Shouldn't the caller awaits for it to complete? Same concern for functions below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most are, just not explicitly. I went through and explicitly called them out as async or converted them to return promises if they didn't already.

npm-scripts.js Outdated
{
cleanWorker();
}
console.error('Failed to download prebuilt binary, building instead', error.message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those logs don't conform to the syntax of other console logs in same file. Also, why are you using then and catch instead of try/catch with await in the try block? We strongly promote that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old habits I guess. This is the first instance of async/await in this file and top level await was added unflagged in node 14.8. Your package.json says you support >=16 so that change should be fine. I'll make the change.

Note that it will explicitly break earlier versions of node, which I just confirmed are out of maintenance, so probably fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup :)

BTW I've pulled your branch and I'm some cosmetic changes so I'll ask you later to cherry-pick them in your branch. So let me please convert those to await syntax.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Today I learned something new about top level await (honestly never attempted it before). In order to convert the syntax to async/await I'd need to convert the whole file into ESM instead of CJS.

So, renamed to npm-scripts.mjs and swap the require for import statements.

I'm happy to do so, but I wanted to check with you before I rewrite a lot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm making many changes into the file, wondering if you'd mind to wait for me to finish

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Sorry, pausing for a few hours, I'll circle back around in the afternoon after meetings and your changes are done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot. I'll ping you here when I'm done.

@ibc
Copy link
Member

ibc commented May 23, 2023

Correct me if I'm wrong but we don't really need those 'prebuild:download' and 'prebuild:unpackage' tasks at all, right? I'm trying to simplify things.

@barlock
Copy link
Author

barlock commented May 23, 2023

We don't, I added them for development of the script itself, so they could be removed.

@ibc
Copy link
Member

ibc commented May 23, 2023

Commit done, but I had to fork your fork: c1f08d8

So if you like, can you git cherry-pick it?

NOTE: I'll be back tomorrow.

@ibc
Copy link
Member

ibc commented May 24, 2023

@barlock I am working in a forked branch/PR here: #1087

There are two concerns I have:

  1. When running "npm install mediasoup" and the script fetches the prebuilt binary (and doesn't build local one) we are NOT using the binary later ¯_(ツ)_/¯. This is because Worker.ts still expects to spawn the binary at worker/out/Release/mediasoup-worker. So I'll modify Worker.ts in a way that it tries first to load the binary from worker/prebuild/Release.
  2. For local development, make command must delete worker/prebuild/* so later it will use the locally build one.
  3. Both bullets above must also apply to Rust.
  4. When running CI tasks, npm ci (which will trigger "postinstall" script as usual) should NOT fetch the prebuilt binary but should always build it locally. I'll add an enc for this.

I'll try to do these things later in my branch.

@barlock
Copy link
Author

barlock commented May 24, 2023

we are NOT using the binary later ¯_(ツ)_/¯.

This PR untar's the downloaded worker to worker/out/Release/mediasoup-worker*. I agree it's probably cleaner code wise to separate those too and allow Worker.tsto pull the binary from aprebuild` directory, though it does run into some complications as you mention.

I am working in a forked branch/PR here: #1087

Great! How would you like me to help with those changes? Would you prefer me to make PRs against that PR or cherry-pick into this one? I believe I also gave you should have the ability to make changes on this branch as a maintainer?

While pulling in changes I'm testing the end-to-end CD flow in my fork that I've been using. GHA are failing in your changes due to require statements moving to the top of npm-scripts. Your current CI logic is to use npm-scripts to perform a specialized install, so it can only use native requires. tsc, tar and node-fetch requires will fail, so I'd moved them into the function/case that used them to avoid that issue.

Alternatively we could just add a npm ci at the top of your steps, but I'm guessing you chose not to do that for a reason.

@ibc
Copy link
Member

ibc commented May 24, 2023

If you don't mind I'm doing too many changes already in my branch. Not sure why I couldn't push to yours. I use gh pr checkout NUMBER command which, sometimes, doesn't create a fork of the PR but this time it did.

Would you mind waiting for me to finish my ongoing stuff and then we discuss pending things?

Regarding use of require instead of import (note: I already fixed those issues), problem is that some deps in npm-scripts files MUST NOT be available when users install mediasoup in production mode (npm install mediasoup --production) since those deps are in devDependencies so they are not installed. So we cannot unconditionally import them in npm-scripts. However, we can use require() dynamically in the file so IMHO it's better if we keep using CommonJS instead of switching to ESM. Definitely we don't want that users have to install all deps in devDependencies when they install mediasoup.

@ibc
Copy link
Member

ibc commented May 24, 2023

NOTE: All credits will be for you in CHANGELOG and announcement and so on.

@barlock
Copy link
Author

barlock commented May 24, 2023

👍 I'll keep checking out your branch and ill wait for you to let me know how and when I can pitch in. Thanks for the credit, 😁 I'm more interested in seeing the change go through and I'm happy it's getting added.

@ibc
Copy link
Member

ibc commented May 25, 2023

I'm almost done in #1087. Let me please close this one.

@ibc ibc closed this May 25, 2023
@ibc
Copy link
Member

ibc commented May 25, 2023

PR is done and all pending stuff done: #1087

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants