Repository navigation
No way to use composite project where outDir is determined by build tool #37378
Description
In #37257 I discussed one way to host TS project references under Bazel, where each build step gets only .d.ts and tsconfig.json files from the referenced projects. This is broken because TS module resolution fails to locate the .d.ts files in their outDir.
In this issue, let's try a different approach for Bazel, to satisfy current TS module resolution, each build step should get the .ts sources of its referenced projects, and the .tsbuildinfo output as well.
Bazel is picky about the setting of --outDir, because it supports cross-compiled builds in distinct output locations. For example if you're on a Mac and building for local platform, the outDir is <workspace root>/bazel-out/darwin-fastbuild/bin. But if you're on a Mac and building for a docker image, you need native binaries to be for the target platform so outDir is <workspace root>/bazel-out/k8-fastbuild/bin and if you want binaries with debug information it's <workspace root>/bazel-out/k8-dbg/bin.
Since the outDir is platform-specific, we don't want to check in a config file that hard-codes it. So we prefer to always have Bazel pass the --outDir option on the command-line to tsc, rather than include it in the tsconfig files.
Now we come to the problem. TypeScript "composite" setting enables up-to-dateness checking in tsc. With this input structure, and trying to tsc -p b
a/a.ts
a/tsconfig.json (no outDir specified; a was compiled with tsc --outDir=bazel-out/darwin-fastbuild/bin/a)
b/b.ts
b/tsconfig.json
bazel-out/darwin-fastbuild/bin/a/a.d.ts
bazel-out/darwin-fastbuild/bin/a/tsconfig.tsbuildinfo
we get b/b.ts(1,17): error TS6305: Output file '/private/var/tmp/_bazel_alx/df60115ea7f2a64e10fb4aa64b7d827f/sandbox/darwin-sandbox/54/execroot/ts_composite/a/a.d.ts' has not been built from source file '/private/var/tmp/_bazel_alx/df60115ea7f2a64e10fb4aa64b7d827f/sandbox/darwin-sandbox/54/execroot/ts_composite/a/a.ts'.
This indicates that TS is looking for a.d.ts next to a.ts. If we hard-code the platform-dependent outDir into a/tsconfig.json like I've done here
https://github.com/alexeagle/ts_composite/pull/5 it solves this problem, but not in a way we can ship.
Note that the compilation of a/tsconfig.json produces a .tsbuildinfo file with a correct "outDir" (based on the --outDir flag that was passed to compilation of a
"options": {
"project": "../../../../a/tsconfig.json",
"outDir": "./",
}
So it seems like the behavior for compiling b is to read the outDir setting from a/tsconfig.json rather than trust the options.outDir reflected in the .tsbuildinfo output.
Activity
[-]--outDir command-line option not reflected in .tsbuildinfo[/-][+]--outDir command-line option in .tsbuildinfo not honored for composite project up-to-date check[/+][-]--outDir command-line option in .tsbuildinfo not honored for composite project up-to-date check[/-][+]options.outDir in .tsbuildinfo not honored for composite project up-to-date check[/+]sheetalkamat commentedon Mar 13, 2020
when doing
tsc -p byou are only building the project b and notaso there is no way to tell if a was built with extra options or not. Whatever is present in the config file ofais the only truth compiler can assume.alexeagle commentedon Mar 16, 2020
Yes, I can see that. The only way
tsccould do what I'm asking here is if the--outDirsetting ofbwas considered to matcha- that is, my output directory scheme always preserves the same directory structure as the sources, so ifawas imported from../aandbis compiled into--outDir=bazel-out/bthen we could know thatamay be inbazel-out/aI think I should take Daniel Rosenwasser (@DanielRosenwasser) up on a suggestion that we have a brief design discussion. After studying this problem for a week, it seems that Bazel's output directory is simply incompatible with typescript project references and one of them needs to change. Do you have a design meeting this week? Thanks!
alexeagle commentedon Mar 17, 2020
I hoped that
--incremental falsewould disable this up-to-date checking (Bazel already does it) but I getis there any other way to avoid this checking?
[-]options.outDir in .tsbuildinfo not honored for composite project up-to-date check[/-][+]No way to use composite project where outDir is determined by build tool[/+]alexeagle commentedon Mar 20, 2020
Ryan Cavanaugh (@RyanCavanaugh) thanks for the discussion today. I noted that you said TS repo itself is affected by this issue, requiring that you have multiple tsconfig files to express the difference between two different build options.
I have another proposal based on some more experimentation.
Take this scenario. We've already compiled lib and now want to compile app.
tsc -p app --outDir bazel-out/mac/appAs we know with TS today, this fails because the reference goes to the
lib/tsconfigwhich doesn't specify an outDir, so TS will look forlib/index.d.tsand not find it.Let's say the semantics for references were expanded (maybe under some new flag) so that
path: ../libcould be resolved relative to the outDir of the current build, meaningbazel-out/mac/app/../lib(I think you suggested in the meeting that this makes sense to you)
This can work, but only if we also add a build step to copy the tsconfig file to the output directory - and we know that's complex because the paths need to be made relative to the new location.
So instead, since there is no
tsconfig.jsonfile in the output directory, we'd want this to resolve one step further tobazel-out/mac/lib/tsconfig.tsbuildinfo.I could imagine that a reference to a tsbuildinfo file could be made to work in place of a reference to a tsconfig file? If so this seems simple and in my basic testing it works.
alexeagle commentedon Mar 21, 2020
#37509 shows the change to resolving reference paths that I'm currently using for prototyping. Along with a hack in Bazel to copy tsconfig into the outDir, this seems to work.
alexeagle commentedon Mar 29, 2020
Sheetal Nandi (@sheetalkamat) FYI we shipped the new TS support for Bazel with a bunch of ugly caveats about this and related bugs
https://bazelbuild.github.io/rules_nodejs/TypeScript#ts_project
What do you or Ryan think of the proposal to resolve reference paths into the outDir?
sheetalkamat commentedon Mar 30, 2020
c:: Ryan Cavanaugh (@RyanCavanaugh) Daniel Rosenwasser (@DanielRosenwasser)
I am not sure if that's what we are looking for.. Resolving project reference from two different places means that (not only project but when processing dependencies and everything) I remember that design mentting we needed more general approach rather than solving just outDir issue?
alexeagle commentedon Jan 26, 2022
As that last reference points out, this problem has gotten worse. Bazel has more potential values for
--outDirand it's really not feasible to require Bazel users to include all of these in therootDirsof a downstream tsc project.RyanCavanaugh commentedon Feb 2, 2022
Daniel and I chatted about this for a while and I think this is sensible and very implementable. Basically just add a new flag (name TBD) where we compute the outDir of a referenced project as follows:
This means that if you had
Then if
fooreferencesbarandfoo'soutDirisout1/foo, under this flag, we compute the path../bar/, and instead of looking forbar/blub.d.ts, we look forout1/foo/../bar/blub.d.ts, which would resolve properlyJust to confirm: Would that solve your problem?
DanielRosenwasser commentedon Feb 2, 2022
I think the key is that this flag only really makes sense when the entire source-tree is mirrored by a corresponding output tree. The above output format would work, and maybe would more specifically look like this for Bazel and similar systems:
But it would not work for the following layout:
So that's why we're curious whether it fits the bill for you.
6 remaining items
RyanCavanaugh commentedon Feb 23, 2022
Alex Eagle (@alexeagle) we'll talk about it again this week, I think your suggestion is pretty sound. A PR would be nice as an anchoring point if you're willing
alexeagle commentedon Mar 9, 2022
Note, the proposal would also solve #22208 since
out1/bar/../foo/blah.d.tswould be resolved before../foo/blah.ts.I'll try to work up a PR, any help would be appreciated to get something closer to landable :)
hack: resolve from outDir
alexeagle commentedon Mar 10, 2022
Ryan Cavanaugh (@RyanCavanaugh) the PR is up. I think if you accept this issue that will clue the bot into putting nicer labels on it :)
sheetalkamat commentedon Mar 22, 2022
Alex Eagle (@alexeagle) I think Ryan Cavanaugh (@RyanCavanaugh) suggested changing outDir computation for the referenced project instead of module resolution. #48190 does changes to module resolution which is not what the suggestion was.
alexeagle commentedon Mar 22, 2022
Project References is just one use case that the PR fixes. As I explained above most Bazel users are simply compiling a project with
tsc -p, and relying on earlier compilations to have populated the outDir with transitive typings.#37378 (comment) is where I think Ryan Cavanaugh (@RyanCavanaugh) agreed to the change in the module resolver.
DanielRosenwasser commentedon Mar 22, 2022
Yeah I think the idea of resolving modules relative to their outputs was what we agreed to in #48042.
sheetalkamat commentedon Mar 22, 2022
But then that doesnt solve project reference issue... how do you know if the d.ts file you got is from project reference? there is definitely some confusion here since i was under impression that we want outDir for the project reference instead as i discussed this with Ryan Cavanaugh (@RyanCavanaugh) this morning.
Not being able to see the file is from project reference will have impact on what the tsc -build does as well as editor experience.
alexeagle commentedon Mar 22, 2022
Could you be more specific what problem this introduces for
tsc -buildor language service users who enable the flag?alexeagle commentedon Apr 4, 2022
Ping! Some large Bazel users are now on a fork of TypeScript to get this.
sheetalkamat commentedon Apr 5, 2022
Appologies that this slipped through other notifications..
tsc --build uses .d.ts ouptut checks of referenced project(Shared) to determine if project(App) needs to be completely rebuilt or just needs timestamps need update.. How is that suppose to work with change to only module resolution..
In editors, when say app project referecnes output d.ts of shared project through module resolution or any other way, we use its source file instead so we can walk through edits without having to save it.. This functionality might be affected with this change since we dont know resolved module is from project reference.
gonzojive commentedon Jun 17, 2022
Where is the fork code?
alexeagle commentedon Jun 17, 2022
Update: we now have better Bazel rules which don't require this at all. They copy all the sources and the node_modules tree to the bazel-out folder, then run
tscwith that working directory, so there is a single root and TypeScript works properly as-is.https://github.com/aspect-build/rules_ts
rc-glean commentedon Oct 10, 2022
Alex Eagle (@alexeagle) when attempting to use a "references" key in a tsconfig.json using Aspect's rules_ts, I'm observing the error:
I'm noting a lack of tsconfig.json's with a references key in the examples with this rules_ts codesearch. Is it possible to use project references with rules_ts? Is there a workaround that hasn't made it into the examples yet?
alexeagle commentedon Oct 10, 2022
Raymond Carino (@rc-glean) it's probably better to file that on the rules_ts repo than on TypeScript, I don't think there's a bug in this repo
edit: I see aspect-build/rules_ts#163 now - when you cross-post an issue to more than one repo, it's good practice to link to the other one :)