Aspnetcore: Support watching multiple projects from command-line

Created on 9 Jul 2017  Â·  20Comments  Â·  Source: dotnet/aspnetcore

Make it possible to watch multiple csproj files that may not be connected in the P2P graph without having to create a "watch.proj" file.

Usage: add -p|--project that takes multiple files.

dotnet watch -p Web/web.csproj -p Test/test.csproj
affected-few area-commandlinetools enhancement help wanted severity-nice-to-have

Most helpful comment

It's in our backlog. We would consider a well-written PR to address this feature.

All 20 comments

@natemcmaster Did you and me talk about being able to do this differently now? If so, should be update the text of this bug?

This is something we can pretty easily implement, and I think it's a useful scenario, esp when/if global tools happen.

By the way, if we do this, we should also do https://github.com/aspnet/DotNetTools/issues/348. This is how I would use it:

dotnet watch -p Web/web.csproj -p Test/test.csproj -x ./build-and-run.sh
#!/usr/bin/env bash
# build-and-run.sh
dotnet build MyApp.sln
dotnet test --no-restore --no-build -p Test/test.csproj
dotnet run --no-restore --no-build -p Web/web.csproj

@DamianEdwards @shanselman What do you guys think about this (and #348)?

I think implementing these together should resolve the issue in #186 as well.

ping

Has this been abandoned?

It's in our backlog. We would consider a well-written PR to address this feature.

I am interested in taking this task. Looking at the code, I see there are a few comments around how one might go about doing this. Are there any additional suggestions or gotchas to be aware of?

One potential gotcha: current working directory. Currently, when you run dotnet watch -p MyProject/MyProject.csproj run, it starts a new process that invokes dotnet run with the working directory set to MyProject/.
https://github.com/aspnet/DotNetTools/blob/9b598446caa060374081b27c00f4c357d064c680/src/dotnet-watch/Program.cs#L152

We did this so users didn't need to invoke dotnet watch -p MyProject/MyProject.csproj -- run -p MyProject/MyProject.csproj.

My proposal: when multiple projects are specified, the inner process dotnet-watch starts should just use the current working directory of dotnet-watch rather than trying to adjust it.

I am still working on this. So far I've got --list with multiple projects working and I'm about to tackle the watch part. Before I get too far I'd like to get feedback on how I'm planning on approaching the watching of multiple projects. I'd really appreciate any comments or suggestions you might have. So here's some of what I did so far and what I'm planning.

Updated MsBuildProjectFinder.FindMsBuildProject

From

MsBuildProjectFinder.FindMsBuildProject(string searchBase, string project)

to

MsBuildProjectFinder.FindMsBuildProjects(string searchBase, IList<string> projectsList)

I did this so that I can better know that multiple projects were passed in as arguments and so that I can always operate on a list of projects, even if the list is just one when you don't pass in any --project arguments or only one project.

Plan for MainInternalAsync

Inside MainInternalAsync I'm planning on looping over each project that gets passed in and adding the DotNetWatcher tasks to a list.

            var watcherTasks = new List<Task>();
            foreach (var projectFile in projectFiles)
            {
                // build file set factory
                // build ProcessSpec
                watcherTasks.Add(new DotNetWatcher(reporter)
                    .WatchAsync(processInfo, fileSetFactory, cancellationToken));
            }

            await Task.WhenAll(watcherTasks);

As for the WorkingDirectory, I was going to set that if the list of projects passed into MainInternalAsync has no items or if it is null — I need to check if it is possible for it to be null.

Testing

I'm still looking at how I might go about writing some functional tests for this. I _think_ I can write a few tests around watch using the WatchableApp. I'm still trying to understand how those work a bit more right now. From what I saw I might have to make some changes to the test harnesses to handle working with multiple projects. Any pointers around this would be really helpful.

Thanks again for any feedback you might have and for the time spent helping me out!

@smerrell ACK saw your comment. I'll take a closer look at this in a few days. I suspect there will be issues in your plan for MainInternalAsync. Currently working on time-sensitive issues so don't have time to go into details. Please ping me again if I don't surface for air by Fri. Nov. 2.

Awesome, thanks a ton! No rush on getting back to me. I'll keep poking around and try to make progress / learn what won't work.

@smerrell the main issue I see with the proposal for MainInternalAsync is that this basically is like launching multiple instances of dotnet-watch, which is something users can already do. This behavior watches multiple projects and runs multiple commands. The behavior I was hoping for watches multiple projects and then runs a single command. I believe this can be implemented by modifying MsBuildFileSetFactory to accept multiple files.

Under the hood, MsBuildFileSetFactory would only need to invoke MSBuild once to collect from all project files. This could be done by adding a new target to DotNetWatch.targets which accepts a semi-colon separated list of project files. Something like this:

dotnet msbuild DotNetWatch.Targets "-p:Projects=p1.csproj;p2.csproj;p3.csproj" -t:GenerateWatchList
  <PropertyGroup>
    <_CollectWatchItemsDependsOn Condition=" '$(Projects)' != '' ">
      _CollectWatchItemsFromProjects;
    </_CollectWatchItemsDependsOn>
  </PropertyGroup>

  <Target Name="_CollectWatchItemsFromProjects" Returns="@(Watch)" >
    <ItemGroup>
      <_Projects Include="$(Projects)" />
    </ItemGroup>

    <MSBuild Projects="@(Projects)"
             Targets="_CollectWatchItems"
             BuildInParallel="true"
             RemoveProperties="Projects" >
      <Output TaskParameter="TargetOutputs" ItemName="Watch" />
    </MSBuild>
  </Target>

Oh I see, that makes a lot of sense now. Thanks for pointing that out! I'll start looking at MsBuildFileSetFactory and learning about what all will need to be done. Thanks a ton for all the advice so far.

@smerrell Did you make any further progress on this after natemcmaster's feedback? This feature would be so nice to have natively without starting multiple projects at once.

Common usecases are probably situations where an API and a MVC application are hosted next to each other, or also when i.e. IdentityServer is hosted as it's own project.

Are there currently any workarounds for this? @natemcmaster mentioned creating a watch.proj, what does that look like or involve? Thanks!

Hey there. Life got in the way and I wasn't able to make a ton of progress on this. What I got stuck on was that I couldn't pass multiple projects to msbuild like Nate suggested. What ended up happening is that msbuild would complain that the ; was an invalid character. I searched as much as I could and found I could escape the semi-colon. Once I did that, instead of getting a list of projects, I got the whole string as a single item. I tried seeing if there were some clever ways I could break the string up inside the targets file but didn't have much luck there. Hopefully this helps move things along.

I'm no longer working for Microsoft. @Pilchie / @glennc are the right people to reach out to if you need more help on this one.

Tagging @nguerrera and @rainersigwald for ideas on how to do this in msbuild logic.

As an FYI, this is currently supported in tye if you'd like to watch multiple projects.

Was this page helpful?
0 / 5 - 0 ratings