-
Notifications
You must be signed in to change notification settings - Fork 236
Change PSES to be buildable as a standalone #647
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a question on some duplicate code I see. Otherwise LGTM!
PowerShellEditorServices.build.ps1
Outdated
|
||
if ($PSVersionTable.PSEdition -ne "Core") { | ||
Add-Type -Assembly System.IO.Compression.FileSystem | ||
} | ||
|
||
function Import-SubmoduleConfigurations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this function needed?
I would think that in RestorePsesModules you could just do:
$modules = Get-Content -Raw "$PSScriptRoot/modules.json" | ConvertFrom-Json
$modules.PSObject.Properties | ForEach-Object {
$params = @{
Name = $_.Name
MinimumVersion = $_.Value.MinimumVersion
MaximumVersion = $_.Value.MaximumVersion
Path = "$PSScriptRoot/modules/"
}
if ($script:SaveModuleSupportsAllowPrerelease) {
$params += @{ AllowPrerelease =
$moduleInstallDetails.AllowPrerelease }
}
Save-Module @params
}
AppVeyor so drunk |
No, those tests were failing on my machine too. I need to work out why that is. |
Ok, I've now run this against vscode-powershell #1252 and verified that it works. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple comments!
} | ||
|
||
# Save each module in the modules.json file | ||
foreach ($moduleName in $moduleInfos.Keys) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have two foreach's one right after the other. Any chance we can combine those?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only problem is that if there's a bad field in the json file, we'll install half the things and then abort.
Ideally, we could have an explicit failure line after the first foreach, but I'm not sure what the best way to detect a failure is or what the best way to fail in a task
is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eh. Not worth implementing. What you have is good :)
/* | ||
System.Console.WriteLine("Test Process PID: " + Process.GetCurrentProcess().Id); | ||
System.Console.WriteLine("Service Process PID: " + this.serviceProcess.Id); | ||
System.Console.ReadKey(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should delete this commented out code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I'd like to wrap it in a conditional, unless you think that will make the code too ugly. I can just imagine wanting to use this again.
var sessionState = InitialSessionState.CreateDefault2(); | ||
sessionState.ImportPSModulesFromPath(scriptAnalyzerModuleInfo.ModuleBase); | ||
InitialSessionState sessionState = InitialSessionState.CreateDefault2(); | ||
sessionState.ImportPSModulesFromPath(GetModulePathFromModuleInfo(scriptAnalyzerModuleInfo)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're just grabbing PSSA from the module path, I wonder if there's a helper variable somewhere that already has the Module path in it. Then we don't need to do this fancy path logic with PSSA just to get the module path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good idea. It may even be a matter of querying the variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking about this. It looks like the way to access this directly is kept internal. It's present as a property of ExecutionContext
, but getting that out of the current runspace would be tricky and possibly error prone.
So another option is to get our powershell session to evaluate $env:PSModulePath
, but I don't think that's very performant. My bigger concern is that, even if we don't consider the possibility that the module path could be changed in some way we don't expect, we still would be trying to import every module in the PSES module directory. Which we don't need in the analysis part and could potentially be quite slow.
But it's also true that I've overengineered the module path resolution bit already. Since we sort of know specifically whether PSSA will be using a versioned path or not. Except that this broke before. And ideally we could eventually move this method out and make it a static utility function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind PSSA can come from anywhere, we definitely don't want to lock it to the bundled version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's a very good point (which admittedly I had forgotten)!
But I'm pretty sure the new code still does that. It still retrieves the module given by Get-Module PSScriptAnalyzer
and gets the path. The problem is that PSModuleInfo
doesn't have a good way of representing its base path; PSModuleInfo.ModuleBase
gives you what could be a versioned path that doesn't work with Import-Module
(or the C# API equivalent used here).
So the only bit I added was to prune the versioned directory from the path if it's the leaf directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh for sure, I should have clarified that I was commenting on the thread and not the actual changes. Side note, I usually just target the psd1
in these situations. Something like this
Path.ChangeExtension(
Path.Combine(
moduleInfo.ModuleBase,
moduleInfo.Name),
".psd1"))
But your approach accounts for modules without a manifest so it's more flexible in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well actually I wanted to do that (or use whatever PSModuleInfo.Path
gave me, which is either a psd1
or a psm1
). Or there's also an overload of InitialSessionState.ImportPSModule
that takes an IEnumerable<ModuleSpecification>
, and there's a constructor for ModuleSpecification
that takes a PSModuleInfo
.
But, it turns out that ImportPSModulesFromPath
calls ModuleUtils.GetDefaultAvailableModuleFiles
internally, which only accepts paths to directories as arguments.
(And the second approach doesn't seem to populate the ModuleSpecification
fields other than Name
.)
I just wish PowerShell exposed a more convenient way to manipulate its modules and module loading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, I always forget the differences between those methods. I typically use InitialSessionState.ImportPSModule
, which does work with psd1
files.
I just wish PowerShell exposed a more convenient way to manipulate its modules and module loading.
👍 👍 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a quest to work out what might be best, I noticed that InitialSessionState.ImportPSModulesFromPath()
in turn just converts the modules it finds into ModuleSpecification
s and feeds them to InitialSessionState.ImportPSModule
.
Anyway, I went to look at why this particular command can take a ModuleSpecification
with everything but the Name
field set to null
, and have the Name
be just a name or a path to a specification, or a whole heap of other things, and just work.
Well, turns out no matter what you do, PowerShell itself just calls Import-Module
(see here).
So I might actually just change our code to do that directly (even though I don't really like the idea of that)...
The method on the sessionState
variable conveniently handles the complexity of runspaces and sessions and making sure we get a good PowerShell instance to import the module into. So I'm going to use the path.
@@ -179,7 +186,7 @@ task TestProtocol -If { !$script:IsUnix} { | |||
task TestHost -If { !$script:IsUnix} { | |||
Set-Location .\test\PowerShellEditorServices.Test.Host\ | |||
exec { & $script:dotnetExe build -c $Configuration -f net452 } | |||
exec { & $script:dotnetExe xunit -configuration $Configuration -framework net452 -verbose -nobuild -x86 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I have no idea what the consequences of this change could be. Because I'm not sure why that was there originally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it's because VSCode use to only be 32bit but now it has a 64bit version so we can probably safely remove this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only the one comment regarding our conversation.
sessionState.ImportPSModulesFromPath(scriptAnalyzerModuleInfo.ModuleBase); | ||
this._scriptAnalyzerModuleInfo = FindPSScriptAnalyzerModule(logger); | ||
InitialSessionState sessionState = InitialSessionState.CreateDefault2(); | ||
sessionState.ImportPSModule(new [] { this._scriptAnalyzerModuleInfo.Path }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked about this offline but I think it would be easier just to put "PSScriptAnalyzer"
here instead of the path received from calling the FindPSScriptAnalyzerModule(logger);
function. This also means that the FindPSScriptAnalyzerModule
function can be totally removed from the code which is also a huge plus in my opinion.
I'd say give it a shot, if it doesn't work then this is fine.
Just got rid of the "always get the latest PSScriptAnalyzer" logic. Now it works on the module path alone (and we get a startup performance boost as well I suppose). Now, if you put a different version (higher or lower) of PSSA on your module path, that will load before ours. Which I think makes more sense than trying to second guess the module path concept. |
return null; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleting an entire function 😍 this is awesome
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably verify that it actually works though. One sec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something but the deleted function seems to be replicating the Import-Module logic for loading a module. That is, ipmo always loads the latest version in the PSModulePath anyway. Unless the caller specifically needs the PSModuleInfo for some reason other than Import-Module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rkeithhill Actually, ipmo does not load the latest version, it loads the first version it finds. This could be a bug in PowerShell Core, but here's what I did:
PS > $env:PSModulePath
/Users/tylerleonhardt/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/usr/local/microsoft/powershell/6.0.2/Modules
## ORDER:
# 1: /Users/tylerleonhardt/.local/share/powershell/Modules
# 2: /usr/local/share/powershell/Modules
# 3: /usr/local/microsoft/powershell/6.0.2/Modules
# What's in #1's Pester
PS > gci /Users/tylerleonhardt/.local/share/powershell/Modules/Pester
Directory: /Users/tylerleonhardt/.local/share/powershell/Modules/Pester
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/9/18 4:14 PM 4.1.1
# What's in #2's Pester
PS > gci /usr/local/share/powershell/Modules/Pester/
Directory: /usr/local/share/powershell/Modules/Pester
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/13/18 12:20 PM 4.3.1
# Load Pester - see it's 4.1.1
PS > ipmo Pester
WARNING: OS Information retrieval is not possible, reports will contain only partial system data
/Users/tylerleonhardt/Desktop/Tech/PowerShell/vscode/PowerShellEditorServices [master ≡]> Get-Module Pester
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 4.1.1 Pester {Add-AssertionOperator, AfterAll, AfterEach, AfterEachFeature...}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that deep down, InitialSessionState.ImportPSModule()
actually spins up a PowerShell instance and calls ipmo anyway. So ideally we don't duplicate the way it works, we just use its logic. I think this should work now with the change I just made. It looks like Import-Module
searches sequentially along the path until it finds a module with the right name and then returns that -- code is here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that is somewhat disappointing behavior. But it would explain why we had that method. Another option would be to import with -MinimumVersion
but that might be a pain to keep current. Anytime we used a new PSSA feature we'd have to update the MinimumVersion
parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - AFAIK that's how "PATH" works too.
Another option would be to import with -MinimumVersion but that might be a pain to keep current
I like this idea. I don't see crazy PSSA changes just for PSES coming our way in the near future and I don't think they'll happen often. It's a small change to make us that much more resilient against someone who still has PSSA 1.0 or something that doesn't work with PSES.
@rjmholt sorry we keep giving you more work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just remembered we're not actually doing Import-Module
it's the sessionState.ImportPSModule
and there doesn't really seem to be a good way to specify MinimumVersion
in its overloads so I've fine with doing what we have in this PR.
I've also noticed that there's a few lines in the |
looks like a zip was added: can you add something to the gitignore so that we don't have this anymore? I'm guessing it's the "artifact" from doing a full |
are you talking about: We can delete that since that module doesn't actually exist. I think David wanted some way to bring in PowerShell Editor Services if it didn't exist but due to some folks being in environments without internet, bundling everything together was the only option. |
Ok, needed to put a PowerShell call back in to verify that we actually have PSScriptAnalyzer available and set all the Anyway, this class I'm touching is ripe for some refactoring (for starters because I don't think we should be calling PowerShell in the constructor at all, and should make a static factory method). |
So my current question is: The intent of this PR is to make PSES a standalone, so it doesn't need to be build by the VSCode client, and as a first step to being used with other clients, and ideally being released onto the PowerShell Gallery. In that case, in Should we change it to ensure it only loads our particular EditorServices module? |
So, the thing is, So this means that, if someone was using PSES for another editor, say vim, they would still put PSES somewhere not on the PSModulePath, run All that said, I agree we eventually want to put PSES in the Gallery to be installable from |
that last paragraph could be a separate PR IMO. |
Agreed about the last paragraph. For now I just want to be able to build PSES by itself -- running it might be complicated, but at least we don't need the VSCode client codebase to do it. As in, I think that should be the scope of this PR. |
This PR also allows the tests to be run & debugged locally which is great :) |
Looks like this is good to go but I wanna merge in #629 first because I'd rather you have merge conflicts then them (not that I'm expecting any). |
Yes agreed |
code for tests. Change test module path so it works Generalise module path resolution Add module parse error, remove test debug code Use simpler module loading API Import ScriptAnalyzer based on path precedence, not version Remove PSES installing itself, remove zip file Remove -EditorServicesVersion reference in ServerTestBase.cs Move Start-PSES script to new location Update testing Start-PSES script path Change URL of Start-PSES script in comment
66dab3e
to
6fff81c
Compare
When PSES builds, it should be responsible for its own modules. So I've added a build rule that populates its
/module/
directory. This moves themodules.json
file over from the VSCode extension as well.So with this, PSES should be generally self contained and ready to run after building.