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

fix(build-tools): restore some tsc dep checking #20971

Merged

Conversation

jason-ha
Copy link
Contributor

@jason-ha jason-ha commented May 3, 2024

fix(build-tools): restore some tsc dep checking
fluid-build-tasks-tsc policy handler was searching for command line of exactly "tsc" which has been mostly abandoned since Node16+ESM pattern.

Create a build database that understands basic build tasks, their output, and their CJS/ESM slant. From that, build approximate predecessor lists to replace "tsc" assumption.

The predecessor list is approximate as a package with multiple ESM (or CJS) build scripts may produce the required output from any one of those scripts. Until tsc incremental is run and generates required inputs, precise dependencies cannot be established. This change does not add a post build policy check for such precise dependencies. So, policy addressee must choose the correct predecessor from a list presented. An example of choices presented:

file failed the "fluid-build-tasks-tsc" policy: packages/test/local-server-tests/package.json
'build:test' task is missing the following dependency: 
        @fluid-internal/client-utils#build:test:jest or @fluid-internal/client-utils#build:test:mocha:cjs or @fluid-internal/client-utils#tsc
        @fluid-internal/mocha-test-setup#tsc
        @fluid-private/test-loader-utils#tsc
        @fluid-private/test-pairwise-generator#tsc
        @fluidframework/aqueduct#api-extractor:commonjs or @fluidframework/aqueduct#build:test:cjs or @fluidframework/aqueduct#tsc
        @fluidframework/build-tools#build:test or @fluidframework/build-tools#tsc
        @fluidframework/cell#api-extractor:commonjs or @fluidframework/cell#build:test:cjs or @fluidframework/cell#tsc

Relocate GenerateEntrypointsCommand to library/commands (new folder) so database can use shared logic to get output files. Package exports logic was further broken out and placed under test. Supporting changes:

  • promote base.ts to library/commands to prevent import cycle
    • move rootPathFlag from flags.ts directly into base.ts

Apply policy corrections. One of note is api-markdown-documenter that builds ESM from script named tsc.

Follow-up: fluid-build-tasks-eslint should be moved off of getDefaultTscTaskDependencies that looks for "tsc".

@github-actions github-actions bot added area: build Build related issues area: examples Changes that focus on our examples area: tests Tests to add, test infrastructure improvements, etc base: main PRs targeted against main branch labels May 3, 2024
fluid-build-tasks-tsc policy handler was searching for command line of exactly "tsc" which has been mostly abandoned since Node16+ESM pattern.

Create a build database that understands basic build tasks, their output, and their CJS/ESM slant. From that build approximate predecessor lists to replace "tsc" assumption.

The predecessor list is approximate as a package with multiple ESM scripts may produce the required output from any one of those scripts. Until tsc incremental is run and generates required inputs precise dependencies cannot be established. This change does not add a post build policy check for such precise dependencies. So policy addressee must choose the correct predecessor from a list presented.

Refactor logic from entrypoints to be used by database to reason able package exports for `generate entrypoints` scripts.

Apply policy corrections. One of note is api-markdown-documenter that builds ESM from script named tsc.

Future: fluid-build-tasks-eslint should be moved off of getDefaultTscTaskDependencies that looks for "tsc".
@jason-ha jason-ha force-pushed the restore-approximate-build-dependencies branch from fc63ea3 to 893747d Compare May 3, 2024 17:20
@jason-ha jason-ha marked this pull request as ready for review May 3, 2024 17:33
Copy link
Member

@tylerbutler tylerbutler left a comment

Choose a reason for hiding this comment

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

No objection to these changes, but I wonder if they're needed. What do they protect us against? Would it be easier to use fluid-build's task graph for some of the logic? Seems like you're producing the equivalent here so maybe fluid-build itself could be more involved in finding problems?

Copy link
Member

Choose a reason for hiding this comment

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

It's a little unclear to me when I would use the fluidBuildDatabase vs. the task graph from fluid-build.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Task graph is defined before build and very quick. fluidBuildDatabase is imprecise until after the build completes as it only understands output from commands and not the inputs. So, it can say that one of the tasks in package A is needed by B, but not precisely which of those tasks. After a complete build, tsbuildinfo will have complete inputs required including and ESM/CJS cross over. From that input list we can add a [post-build] policy check on task config that is 100% accurate.

Copy link
Contributor

Choose a reason for hiding this comment

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

After a complete build, tsbuildinfo will have complete inputs required including and ESM/CJS cross over. From that input list we can add a [post-build] policy check on task config that is 100% accurate.

And that input list is what fluidBuildDatabase ends up with, I suppose? Might be worth putting copying this explanation (and a bit more? It's still not 100% clear to me 😅 ) to the class tsdocs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mispoke a bit. FluidBuildDatabase doesn't currently end up with that complete information even after build and probably won't. FluidBuildDatabase only has the outputs of scripts. I am adding a comment noting that.
After build we can read the inputs from tsbuildinfo and search FluidBuildDatabase's output knowledge to find source scripts. I don't think we need to store the input into the database, too.

examples/apps/data-object-grid/package.json Show resolved Hide resolved
// Generic build:test script should be replaced by :esm or :cjs specific versions.
// "tsc" would be nice to eliminate from here, but plenty of packages still focus
// on CommonJS.
"build:test": ["typetests:gen", "tsc", "api-extractor:commonjs", "api-extractor:esnext"],
Copy link
Member

Choose a reason for hiding this comment

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

This seems like an odd combination. The test script name isn't specific, so if the package builds both commonjs and esm tests, shouldn't both be built by running build:test?

Suggested change
"build:test": ["typetests:gen", "tsc", "api-extractor:commonjs", "api-extractor:esnext"],
"build:test": ["build:test:cjs", "build:test:esnext"],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If package builds both esm and cjs tests, then those individual test build scripts should have their appropriate dependencies and this isn't needed. This config should be removed, but first the config needs moved to the places that still build tests from build:test. I was doing the minimal changes that are happy with current and new policy and avoiding looking at individual packages.

Copy link
Contributor

@alexvy86 alexvy86 May 9, 2024

Choose a reason for hiding this comment

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

and this isn't needed

I'd say maybe we still want it? We have the single build/compile that covers commonjs and build:esnext, so having the same pattern for tests makes sense to me. That said, for another PR.

tools/getkeys/.eslintrc.cjs Show resolved Hide resolved
Comment on lines -115 to +116
"build:tsc": [
"^tsc"
"tsc": [
"^build:esnext"
Copy link
Member

Choose a reason for hiding this comment

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

Why not rename the script to build:esnext instead of tsc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I was not touching scripts and potentially breaking workflows I didn't know about. Leave that for later/others. tools is outside of the fits a standard pattern world and that is not what this task is about.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see any references to build:tsc in the repo other than the npm script above and the task entry here... not sure what either of them is even accomplishing since nothing else triggers them through fluid build. If we change the task entry I think we want to remove the npm script too. @Josmithr might be able to confirm?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need to remove the script entry even if there was a tie in from something else to build:tsc. build:tsc runs tsc which the fluid build system automatically recognizes as a dependency. So, any build:tsc task in the system has these manually configured dependencies.
If build:tsc script is excessive (unused), then it could be removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think it is unused. It's defined and has a task entry here, but no other task/script ever runs it as far as I can tell. Can be removed separately though.

tools/getkeys/package.json Outdated Show resolved Hide resolved
if (typeof value === "string") {
if (entry === "types") {
for (const [key, apiTagLevel] of mapQueryPathToApiTagLevel.entries()) {
// eslint-disable-next-line max-depth
Copy link
Contributor

@Josmithr Josmithr May 9, 2024

Choose a reason for hiding this comment

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

Nit: extracting the body of either for-loop into its own function might make this disable unecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would. I felt it was better to read the contents of the function here rather than extract for this aggressive rule (my opinion) and try to name/explain the function and handle the return. ?
We can also mitigate rule violation by making line 47 "if" negative and "continue"ing which would bring this all up one level, but continue can be harder to follow.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not particularly concerned about the line length rule itself, more commenting on the fact that there is a lot of logical nesting going on here. I'm wondering if it might make it a bit more readable to pull out pieces and document what they do in isolation. I'm honestly not sure if it would, but there is a fair amount going on here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went in to see if comments would help clear things up and that lead me to the things that aren't handled as we don't author outside of a pattern. I started editing to handle those and found a way to move a little out and I think it is better now.

"fluidBuild": {
"tasks": {
"build:test": [
"^tsc",
Copy link
Contributor

Choose a reason for hiding this comment

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

For my/reviewer reference, packages have build:test depend on ^tsc + ^api-extractor:commonjs when they are commonjs and on ^build:esnext + ^api-extractor:esnext when they're esm.

Tangent: we should probably make this package esm, but not in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. And we prefer to have build:test:cjs|esm for clarity. build:test can exist to run either or both of those but doesn't need dependencies itself. We just need to make sure there are no more build:test that do things directly.

Re Tangent: I knew about this. For now, I thought it was good to have something that runs CommonJS. There are some tasks to make sure we have correct coverage on ESM / CJS.

Copy link
Contributor

@alexvy86 alexvy86 left a comment

Choose a reason for hiding this comment

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

Haven't looked at the fluidBuildDatabase or the policy nor fluidBuildTasks.ts; the rest looks ok except for potentially one comment left independently above.

Copy link
Contributor

@alexvy86 alexvy86 left a comment

Choose a reason for hiding this comment

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

A few more comments, nothing critical. I can follow the general idea of what's happening and it seems fine, I just can't quite follow the details in my head.

I'll say that several things in these packages (like hasTaskDependency) would benefit from unit tests. I'll leave it up to you if it makes sense to add some here or capture work items for the backlog.

@alexvy86
Copy link
Contributor

Which part is not as followable. This seems like something that should be sufficiently overviewed/explained somewhere (and not just in PR description).

It's mostly lack of familiarity with a good chunk of the code this is touching or calling. Having to go several function calls deep in code that's new to me to actually understand what something in your changes is doing, a few times per file or per function, starts overwhelming my capacity to keep a solid mental model of the system as a whole.

I'd say docs on the helper functions (both newly added ones and pre-existing ones you used) would help, that'd be a good place for further explanation so I could either trust the functions and not have to go through them line by line to understand them, or if I decide to still go through them, at least have some baseline of what I'm expecting the code there to be doing.

pkg: Package,
commandLine: string,
): { files: AbsoluteFilePath[]; type: ModuleType | undefined } | undefined {
if (!commandLine.startsWith("flub generate entrypoints")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this check because this function runs as a part of flub generate entrypoints? This feels fragile to me. At the very least, it seems like we should extract this into a constant with some docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not a part of flub generate entrypoints. This is helper to derive what output a flub command would generate. generate entrypoints is the only one I know we need.
It would be better if *Task classes or some part of them could be leveraged. I checked that out briefly but there are too many dependencies and complications.
Same for the fluid commands. Maybe if I understood oclif better there would be a better refactor to reuse the command processing. There is also a complication with code organization and cyclical import dependencies that make relocating Commands hard. Best I could do was extract some common logic. Having said that I might actually be able to clear up the cycle and do something better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Relocated command and shared more logic

- tighten up typing with readonly
- some refactoring for readability
- revise isTypeOnly handling to support more exports structuring patterns
- add unit tests for packageExports.ts
to library under new commands folder for logic reuse
- promote base.ts to library to prevent import cycle
  - move rootPathFlag from flags.ts
@jason-ha jason-ha requested a review from Josmithr May 12, 2024 17:22
Comment on lines +27 to +31
const rootPathFlag = Flags.custom({
description: "Root directory of the Fluid repo (default: env _FLUID_ROOT_).",
env: "_FLUID_ROOT_",
hidden: true,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I was already writing that this felt better with the rest of the reusable flags but then noticed it's part of the BaseCommand that every other one extends and it's not referenced anywhere else, so I think moving it here makes sense.

/**
* Generates type declarations files for Fluid Framework APIs to support API levels (/alpha, /beta. etc.).
*/
export class GenerateEntrypointsCommand extends BaseCommand<
Copy link
Contributor

Choose a reason for hiding this comment

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

I find it weird to move the whole command (and BaseCommand) to library/ instead of just the necessary helper functions (cursory inspection seemed to suggest we can move just some functions but maybe I missed something). Wouldn't block on this but seems worth revisiting. If it's absolutely necessary, so be it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving just some helpers is what I originally was doing. The real thing that we want to share is the argument processing up to the point of knowing what files are generated. We can't share the argument processing as it is too opaque (at least I couldn't find the way to extract that). So, the next best is to have very similar and somewhat tied together processing in one file and for that I needed the flags out of Command. It didn't seem readable to separate flags from the Command. So, now they can be in one place and we can access two parts as needed.
Another option was to get repoPolicyCheck out of library, but that appeared more complicated.

actual: Map<TKeys, TValues>,
expected: Map<TKeys, TValues>,
): void {
assert.hasAllKeys(actual, [...expected.keys()]);
Copy link
Contributor

Choose a reason for hiding this comment

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

What scenario does assert.hasAllKeys cover that isn't handled by the loop below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assert.hasAllKeys confirms that keys are identical - those keys and only those keys (I think the name could be better). The below would allow extra keys.

Copy link
Contributor

@Josmithr Josmithr May 13, 2024

Choose a reason for hiding this comment

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

You could simplify all of this as something like

	assert.deepEquals([...actual.entries()], [...expected.entries()]);

I don't know if the error messages would be as useful though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I liked the output of hasAllKeys. And then there is some nicety to being able to break/debug on single value. Thanks though :)

Copy link
Contributor

@Josmithr Josmithr left a comment

Choose a reason for hiding this comment

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

My overall understanding of how fluid-build functions is pretty limited. But given that, these changes seem reasonable to me.

I would really love to see us invest in moving away from fluid-build more generally, though. The logic involved keeps expanding, and the set of people who know what's going on is very small.

@jason-ha jason-ha enabled auto-merge (squash) May 14, 2024 00:37
@jason-ha jason-ha merged commit 05b3ebc into microsoft:main May 14, 2024
46 checks passed
@jason-ha jason-ha deleted the restore-approximate-build-dependencies branch May 14, 2024 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: build Build related issues area: examples Changes that focus on our examples area: tests Tests to add, test infrastructure improvements, etc base: main PRs targeted against main branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants