diff --git a/.ado/ci.yml b/.ado/ci.yml index d2205e029..f063bd252 100644 --- a/.ado/ci.yml +++ b/.ado/ci.yml @@ -67,7 +67,7 @@ jobs: - script: | cd $(Build.SourcesDirectory)/azure-quantum - pip install .[all] + pip install .[qiskit,cirq,qsharp,dev] pytest --cov-report term --cov=azure.quantum --junitxml test-output-azure-quantum.xml $(Build.SourcesDirectory)/azure-quantum displayName: Run azure-quantum unit tests diff --git a/.ado/publish.yml b/.ado/publish.yml index fd6f6338f..e77d77b79 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -117,7 +117,7 @@ extends: - script: | cd $(Build.SourcesDirectory)/azure-quantum - pip install .[all] + pip install .[qiskit,cirq,qsharp,dev] pytest --cov-report term --cov=azure.quantum --junitxml test-output-azure-quantum.xml $(Build.SourcesDirectory)/azure-quantum displayName: Run Unit-tests diff --git a/azure-quantum/MANIFEST.in b/azure-quantum/MANIFEST.in index 4baebbfe3..879ba3554 100644 --- a/azure-quantum/MANIFEST.in +++ b/azure-quantum/MANIFEST.in @@ -3,7 +3,6 @@ include *.py include *.md recursive-include azure *.py exclude MANIFEST.in -exclude environment.yml recursive-exclude azure *.typed recursive-exclude eng * recursive-exclude tests * diff --git a/azure-quantum/README.md b/azure-quantum/README.md index 143167bfb..bcb346de4 100644 --- a/azure-quantum/README.md +++ b/azure-quantum/README.md @@ -79,6 +79,48 @@ result = job.get_results() You can find example Python scripts that use the Azure Quantum Python API in the [examples](https://github.com/microsoft/qdk-python/tree/main/azure-quantum/examples) directory. +## Development Setup ## + +For developers who want to contribute to this package or run tests locally, follow these steps: + +### Prerequisites + +- Python 3.9 or later +- Git +- Powershell + +### Setting up the development environment + +1. Clone the repository: + ```bash + git clone https://github.com/microsoft/qdk-python.git + cd qdk-python/azure-quantum + ``` + +2. Set up a virtual environment and install development dependencies: + ```powershell + # On Windows (PowerShell) + .\eng\Setup-Dev-Env.ps1 + ``` + +3. Run the tests: + ```bash + pytest tests/unit/ + ``` + +4. (Optional) Install additional provider dependencies: + ```bash + # For specific providers + pip install -e .[pulser,quil] + + # For all providers (requires Rust toolchain for PyQuil) + pip install -e .[all] + ``` + +### Running Tests + +The development environment includes pytest for running unit tests. See [tests/README.md](tests/README.md) for detailed testing instructions. + ## Contributing ## For details on contributing to this package, see the [contributing guide](https://github.com/microsoft/qdk-python/blob/main/CONTRIBUTING.md). diff --git a/azure-quantum/eng/Record-Tests.ps1 b/azure-quantum/eng/Record-Tests.ps1 index 238dcff41..7fd31d2bf 100644 --- a/azure-quantum/eng/Record-Tests.ps1 +++ b/azure-quantum/eng/Record-Tests.ps1 @@ -20,7 +20,14 @@ try { Push-Location (Join-Path $PSScriptRoot "../tests/") - conda activate azurequantum + # Activate virtual environment if it exists + $VenvPath = "../venv" + if (Test-Path $VenvPath) { + Write-Host "Activating virtual environment..." + & "$VenvPath\Scripts\Activate.ps1" + } else { + Write-Warning "Virtual environment not found at $VenvPath. Please run Setup-Dev-Env.ps1 first." + } if ([string]::IsNullOrEmpty($TestFilter)) { pytest diff --git a/azure-quantum/eng/Setup-Dev-Env.ps1 b/azure-quantum/eng/Setup-Dev-Env.ps1 index 918d95a58..d2b2d268d 100644 --- a/azure-quantum/eng/Setup-Dev-Env.ps1 +++ b/azure-quantum/eng/Setup-Dev-Env.ps1 @@ -7,11 +7,30 @@ try { Push-Location (Join-Path $PSScriptRoot "../") - conda env create -f environment.yml - conda env update -f environment.yml --prune - conda activate azurequantum + # Create virtual environment if it doesn't exist + $VenvPath = "venv" + if (-not (Test-Path $VenvPath)) { + Write-Host "Creating virtual environment..." + python -m venv $VenvPath + } - pip install -e .[qiskit,cirq] + # Activate virtual environment + Write-Host "Activating virtual environment..." + & ".\$VenvPath\Scripts\Activate.ps1" + + # Upgrade pip and install build tools + Write-Host "Upgrading pip and installing build tools..." + python -m pip install --upgrade pip setuptools wheel + + # Install package in editable mode with optional dependencies + Write-Host "Installing package in editable mode..." + pip install -e .[qiskit,cirq,qsharp,dev] + + Write-Host "" + Write-Host "Development environment setup complete!" + Write-Host "To install additional optional dependencies later, run:" + Write-Host " pip install -e .[pulser,quil] # for specific providers" + Write-Host " pip install -e .[all] # for all providers (requires Rust toolchain)" } finally { diff --git a/azure-quantum/environment.yml b/azure-quantum/environment.yml deleted file mode 100644 index 5942f90e4..000000000 --- a/azure-quantum/environment.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: azurequantum -channels: - - quantum-engineering - - conda-forge -dependencies: - - python=3.9 - - pip>=22.3.1 - - pytest>=7.1.2 - - pytest-regressions - - pip: - - -e .[all] diff --git a/azure-quantum/requirements-dev.txt b/azure-quantum/requirements-dev.txt index 6a92c4ebc..a25f55d04 100644 --- a/azure-quantum/requirements-dev.txt +++ b/azure-quantum/requirements-dev.txt @@ -1,3 +1,4 @@ +pytest>=7.1.2 vcrpy>=4.3.1 # fixes https://github.com/kevin1024/vcrpy/issues/688 azure-devtools>=1.2.0,<2.0 -graphviz>=0.20.1 \ No newline at end of file +graphviz>=0.20.1 diff --git a/azure-quantum/tests.live/Install-Artifacts.ps1 b/azure-quantum/tests.live/Install-Artifacts.ps1 index e07717c72..911c52079 100644 --- a/azure-quantum/tests.live/Install-Artifacts.ps1 +++ b/azure-quantum/tests.live/Install-Artifacts.ps1 @@ -3,19 +3,16 @@ <# .SYNOPSIS - Install-Artifacts: set up a Python environment using Anaconda + Install-Artifacts: set up a Python environment using virtual environments #> $PackageDir = Split-Path -parent $PSScriptRoot; $PackageName = $PackageDir | Split-Path -Leaf; $RootDir = Split-Path -parent $PackageDir; -Import-Module (Join-Path $RootDir "build" "conda-utils.psm1"); -Import-Module (Join-Path $RootDir "build" "package-utils.psm1"); +Import-Module (Join-Path $RootDir "build" "venv-utils.psm1") -Force; +Import-Module (Join-Path $RootDir "build" "package-utils.psm1") -Force; -# Enable conda hook -Enable-Conda - -NewCondaEnvForPackage -PackageName $PackageName +New-VenvForPackage -PackageName $PackageName if (-not $Env:PYTHON_OUTDIR) { "" | Write-Host @@ -23,7 +20,8 @@ if (-not $Env:PYTHON_OUTDIR) { "== We will install $PackageName from source." | Write-Host "" | Write-Host - Install-PackageInEnv -PackageName "$PackageName[all]" -FromSource $True + $PackageWithExtras = "$PackageName[qiskit,cirq,qsharp,dev]" + Install-PackageInEnv -PackageName $PackageWithExtras -FromSource $True "" | Write-Host "== $PackageName installed from source. ==" | Write-Host @@ -34,7 +32,8 @@ if (-not $Env:PYTHON_OUTDIR) { "== To use build artifacts, download the artifacts locally and point the variable to this folder." | Write-Warning "" | Write-Warning Exit 1 -} +} + # this condition is used by the E2E Live test pipeline elseif ($Env:PICK_QDK_VERSION -eq "auto") { "== Installing latest published $PackageName package from PyPI..." | Write-Host @@ -43,9 +42,9 @@ elseif ($Env:PICK_QDK_VERSION -eq "auto") { "== Preparing environment to use artifacts with version '$Env:PYTHON_VERSION' " | Write-Host "== from '$Env:PYTHON_OUTDIR'" | Write-Host if ($Env:PYTHON_VERSION) { - $NameAndVersion = "$PackageName[all]==$($Env:PYTHON_VERSION)" + $NameAndVersion = "$PackageName[qiskit,cirq,qsharp,dev]==$($Env:PYTHON_VERSION)" } else { - $NameAndVersion = "$PackageName[all]" + $NameAndVersion = "$PackageName[qiskit,cirq,qsharp,dev]" } Install-PackageInEnv -PackageName $NameAndVersion -FromSource $False -BuildArtifactPath $Env:PYTHON_OUTDIR diff --git a/azure-quantum/tests.live/Run.ps1 b/azure-quantum/tests.live/Run.ps1 index ef9340f94..1873ee27d 100644 --- a/azure-quantum/tests.live/Run.ps1 +++ b/azure-quantum/tests.live/Run.ps1 @@ -18,7 +18,7 @@ Get-ChildItem env:AZURE*, env:*VERSION, env:*OUTDIR | ForEach-Object { $PackageDir = Split-Path -parent $PSScriptRoot; $PackageName = $PackageDir | Split-Path -Leaf; $RootDir = Split-Path -parent $PackageDir; -Import-Module (Join-Path $RootDir "build" "conda-utils.psm1"); +Import-Module (Join-Path $RootDir "build" "venv-utils.psm1"); Import-Module (Join-Path $RootDir "build" "package-utils.psm1"); if ($True -eq $SkipInstall) { @@ -27,24 +27,6 @@ if ($True -eq $SkipInstall) { & (Join-Path $PSScriptRoot Install-Artifacts.ps1) } -Enable-Conda - -# Try activating the azurequantum conda environment -if ([string]::IsNullOrEmpty($PackageName) -or ($PackageName -eq "azure-quantum")) { - try { - $EnvExists = conda env list | Select-String -Pattern "azurequantum " | Measure-Object | Select-Object -Exp Count - if ($EnvExists) { - conda activate azurequantum - } - } - catch { - Write-Host "##[warning]Failed to active conda environment." - } -} - -$EnvName = GetEnvName -PackageName $PackageName -Use-CondaEnv $EnvName - function PyTestMarkExpr() { param ( [string[]] $AzureQuantumCapabilities diff --git a/azure-quantum/tests/README.md b/azure-quantum/tests/README.md index f999bfdb6..e62e766ca 100644 --- a/azure-quantum/tests/README.md +++ b/azure-quantum/tests/README.md @@ -2,7 +2,19 @@ ## Environment Pre-reqs -Refer to [the parent README](../README.md) for how to prepare the development environment before running the unit tests. +Before running the unit tests, set up your development environment using the venv-based setup: + +### On Windows (PowerShell): +```powershell +.\eng\Setup-Dev-Env.ps1 +``` + +This will install the package with common optional dependencies (qiskit, cirq, qsharp). To install additional provider dependencies, run: +```powershell +./venv/Scripts/activate # Activate the virtual environment first +pip install -e .[pulser,quil] # for specific providers +pip install -e .[all] # for all providers (requires Rust toolchain) +``` ### Environment variables for Recording and Live-Tests diff --git a/build/conda-utils.psm1 b/build/conda-utils.psm1 deleted file mode 100644 index 6f56c7995..000000000 --- a/build/conda-utils.psm1 +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Includes utility cmdlets for working with conda both locally, - and in Azure Pipelines and other hosted environments. -#> - -function Test-CondaEnabled { - <# - .SYNOPSIS - Tests if conda has been enabled for shell - use (e.g.: if conda activate will work). - #> - - # The shell hook for conda creates a new cmdlet called - # Invoke-Conda that is then aliased to `conda`. If the - # full name (without an alias) is available, then the hook - # has been run already. - if ($null -ne (Get-Command Invoke-Conda -ErrorAction SilentlyContinue)) { - return $true; - } - return $false; -} - -function Enable-Conda { - <# - .SYNOPSIS - Attempts to enable conda for shell usage, first using - the CONDA environment variable for the path, falling back - to PATH, and then failing. - If conda is already enabled, this cmdlet does nothing, - so that this cmdlet is idempotent. - #> - - if (Test-CondaEnabled) { return $true; } - - if ("$Env:CONDA" -ne "") { - # Try and run the shell hook from the path nominated - # by CONDA. - - # On Windows, this is $Env:CONDA/Scripts/conda.exe, while on - # Linux and macOS, this is $Env:CONDA/bin/conda. - - if ($IsWindows) { - $condaBin = Join-Path $Env:CONDA "Scripts" "conda.exe"; - } else { - $condaBin = Join-Path $Env:CONDA "bin" "conda"; - } - - if ($null -eq (Get-Command $condaBin -ErrorAction SilentlyContinue)) { - Write-Error "##[error] conda binary was expected at $condaBin (CONDA = $Env:CONDA), but was not found."; - throw; - } - - (& $condaBin shell.powershell hook) | Out-String | Invoke-Expression; - } else { - (conda shell.powershell hook) | Out-String | Invoke-Expression; - } -} - -function Use-CondaEnv { - param ( - [string] $EnvName - ) - <# - .SYNOPSIS - Activates a conda environment, reporting the new configuration - after having done so. If conda has not already been enabled, this - cmdlet will enable it before activating. - #> - - Enable-Conda - # NB: We use the PowerShell cmdlet created by the conda shell hook here - # to avoid accidentally using the conda binary. - Enter-CondaEnvironment $EnvName - Write-Host "##[info]Activated Conda env: $(Get-PythonConfiguration | Out-String)" -} - -function Install-Package() { - param( - [string] $EnvName, - [string] $PackageName, - [bool] $FromSource - ) - # Activate env - Use-CondaEnv $EnvName - # Install package - if ($True -eq $FromSource) { - $ParentPath = Split-Path -parent $PSScriptRoot - $AbsPackageDir = Join-Path $ParentPath $PackageName - Write-Host "##[info]Install package $AbsPackageDir in development mode for env $EnvName" - pip install -e $AbsPackageDir - } else { - Write-Host "##[info]Install package $PackageName for env $EnvName" - pip install $PackageName - } -} - -function Get-PythonConfiguration { - <# - .SYNOPSIS - Returns a table describing the current Python configuration, - useful for diagnostic purposes. - #> - - $table = @{}; - - $python = (Get-Command python -ErrorAction SilentlyContinue); - if ($null -ne $python) { - $table["PythonLocation"] = $python.Source; - try { - $table["PythonVersion"] = & $python --version; - } catch { } - } - - # If the CONDA environment variable is set, allow that to override - # the local PATH. - $conda = Get-Command conda -ErrorAction SilentlyContinue; - - if ($null -ne $conda) { - $table["CondaLocation"] = $conda.Source; - try { - $table["CondaVersion"] = & $conda --version; - } catch { } - } - - # If the conda hook has already been run, we can get some additional - # information for the table. - if (Test-CondaEnabled) { - try { - $env = Get-CondaEnvironment | Where-Object -Property Active; - if ($null -ne $env) { - $table["CondaEnvName"] = $env.Name; - $table["CondaEnvPath"] = $env.Path; - } - } catch { } - } - - $table | Write-Output; -} \ No newline at end of file diff --git a/build/package-utils.psm1 b/build/package-utils.psm1 index 0e96889e6..06e0243f3 100644 --- a/build/package-utils.psm1 +++ b/build/package-utils.psm1 @@ -1,168 +1,53 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. -Import-Module (Join-Path $PSScriptRoot "conda-utils.psm1"); +<# + .SYNOPSIS + PowerShell utilities for package management +#> -function PackagesList{ - param( - [string] $PackageName - ) - if ('' -eq $PackageName) { - # If no package dir is specified, find all packages that contain an environment.yml file - $parentPath = Split-Path -parent $PSScriptRoot - $PackageNames = Get-ChildItem -Path $parentPath -Recurse -Filter "environment.yml" | Select-Object -ExpandProperty Directory | Split-Path -Leaf - Write-Host "##[info]No PackageDir. Setting to default '$PackageNames'" - } else { - $PackageNames = @($PackageName) - } - return $PackageNames -} - -function Install-Package() { +function Install-PackageInEnv { + <# + .SYNOPSIS + Install a package in the current environment + .PARAMETER PackageName + The name of the package to install + .PARAMETER FromSource + Whether to install from source (editable install) + .PARAMETER BuildArtifactPath + Path to build artifacts (optional) + #> param( - [string] $EnvName, + [Parameter(Mandatory=$true)] [string] $PackageName, - [bool] $FromSource, - [string] $BuildArtifactPath + + [Parameter(Mandatory=$false)] + [bool] $FromSource = $false, + + [Parameter(Mandatory=$false)] + [string] $BuildArtifactPath = "" ) - # Activate env - Use-CondaEnv $EnvName - # Show all installed packages - conda list - # Install package - if ($True -eq $FromSource) { + + if ($FromSource) { + Write-Host "Installing '$PackageName' from source (editable install)..." $ParentPath = Split-Path -parent $PSScriptRoot $AbsPackageName = Join-Path $ParentPath $PackageName Write-Host "##[info]Install package $AbsPackageName in development mode for env $EnvName" pip install -e $AbsPackageName - } elseif ("" -ne $BuildArtifactPath) { - Write-Host "##[info]Installing $PackageName from $BuildArtifactPath" - Push-Location $BuildArtifactPath - pip install $PackageName --pre --find-links $BuildArtifactPath - if ($LASTEXITCODE -ne 0) { throw "Error installing qsharp-core wheel" } - Pop-Location + } elseif (-not [string]::IsNullOrEmpty($BuildArtifactPath)) { + Write-Host "Installing '$PackageName' from build artifacts at '$BuildArtifactPath'..." + $WheelFiles = Get-ChildItem -Path $BuildArtifactPath -Filter "*.whl" + if ($WheelFiles.Count -gt 0) { + pip install $WheelFiles[0].FullName + } else { + Write-Warning "No wheel files found in '$BuildArtifactPath'. Installing from PyPI instead." + pip install $PackageName + } } else { - Write-Host "##[info]Install latest package $PackageName for env $EnvName from PyPI" + Write-Host "Installing '$PackageName' from PyPI..." pip install $PackageName } } -function GetEnvName { - param ( - [string] $PackageName, - [string] $CondaEnvironmentSuffix - ) - # The environment name is based on $PackageName, but - # 1. if the name includes the version via name==version, remove the version, - # 2. add conda env suffix - # 3. remove "-" since its invalid - # 4. remove ".aio" and use the same environment as the non-async version - return ($PackageName.Split("[")[0].Split("=")[0] + $CondaEnvironmentSuffix).replace("-", "").replace(".aio", "") -} - -function Install-PackageInEnv { - param ( - [string] $PackageName, - [string] $CondaEnvironmentSuffix, - [bool] $FromSource, - [string] $BuildArtifactPath - ) - $PackageNames = PackagesList -PackageName $PackageName - foreach ($PackageName in $PackageNames) { - $EnvName = GetEnvName -PackageName $PackageName -CondaEnvironmentSuffix $CondaEnvironmentSuffix - Install-Package -EnvName $EnvName -PackageName $PackageName -FromSource $FromSource -BuildArtifactPath $BuildArtifactPath - } -} - -function New-CondaEnvironment { - param( - [string] $PackageName, - [string] $CondaEnvironmentSuffix - ) - <# - .SYNOPSIS - Create Conda environment(s) for given package directories - Optionally, use CondaEnvironmentSuffix to specify a special environment file with name environment.yml. - If PackageName is not specified, get all packages in the root directory. - #> - - $PackageNames = PackagesList -PackageName $PackageName - foreach ($PackageName in $PackageNames) { - NewCondaEnvForPackage -PackageName $PackageName -CondaEnvironmentSuffix $CondaEnvironmentSuffix - } -} - -function NewCondaEnvForPackage { - param( - [string] $PackageName, - [string] $CondaEnvironmentSuffix - ) - <# - .SYNOPSIS - Create Conda environment(s) for given package directories - Optionally, use CondaEnvironmentSuffix to specify a special environment file with name environment.yml. - #> - - $parentPath = Split-Path -parent $PSScriptRoot - $EnvPath = Join-Path $parentPath $PackageName "environment$CondaEnvironmentSuffix.yml" - $EnvName = GetEnvName -PackageName $PackageName -CondaEnvironmentSuffix $CondaEnvironmentSuffix - - # Check if environment already exists - $EnvExists = conda env list | Select-String -Pattern "$EnvName " | Measure-Object | Select-Object -Exp Count - - # If it exists, skip creation - if ($EnvExists -eq "1") { - Write-Host "##[info]Skipping creating $EnvName; env already exists." - - } else { - # If it does not exist, create conda environment - Write-Host "##[info]Build '$EnvPath' for Conda environment $EnvName" - conda env create --quiet --file $EnvPath - } -} - -function New-Wheel() { - param( - [string] $EnvName, - [string] $Path, - [string] $OutDir - ); - - Push-Location $Path - # Set environment vars to be able to run conda activate - Write-Host "##[info]Pack wheel for env '$EnvName'" - # Activate env - Use-CondaEnv $EnvName - # Create package distribution - python setup.py bdist_wheel sdist --formats=gztar - - if ($LastExitCode -ne 0) { - Write-Host "##vso[task.logissue type=error;]Failed to build $Path." - $script:all_ok = $False - } else { - if ($OutDir -ne "") { - Write-Host "##[info]Copying wheel to '$OutDir'" - Copy-Item "dist/*.whl" $OutDir/ - Copy-Item "dist/*.tar.gz" $OutDir/ - } - } - Pop-Location -} - - -function Invoke-Tests() { - param( - [string] $PackageName, - [string] $EnvName - ) - $ParentPath = Split-Path -parent $PSScriptRoot - $AbsPackageDir = Join-Path $ParentPath $PackageName - "##[info]Test package $AbsPackageDir and run tests for env $EnvName" | Write-Host - # Activate env - Use-CondaEnv $EnvName | Write-Host - # Install testing deps - python -m pip install --upgrade pip | Write-Host - pip install pytest pytest-azurepipelines pytest-cov | Write-Host - # Run tests - $PkgName = $PackageName.replace("-", ".") - pytest --cov-report term --cov=$PkgName --junitxml test-output-$PackageName.xml $AbsPackageDir | Write-Host - return ($LASTEXITCODE -eq 0) -} \ No newline at end of file +# Export functions +Export-ModuleMember -Function Install-PackageInEnv \ No newline at end of file diff --git a/build/venv-utils.psm1 b/build/venv-utils.psm1 new file mode 100644 index 000000000..911a5f708 --- /dev/null +++ b/build/venv-utils.psm1 @@ -0,0 +1,114 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# + .SYNOPSIS + PowerShell utilities for managing Python virtual environments +#> + +function Get-VenvName { + <# + .SYNOPSIS + Get the virtual environment name for a package + .PARAMETER PackageName + The name of the package + #> + param( + [Parameter(Mandatory=$true)] + [string] $PackageName + ) + + if ([string]::IsNullOrEmpty($PackageName) -or ($PackageName -eq "azure-quantum")) { + return "venv" + } + return "venv-$PackageName" +} + +function New-VenvForPackage { + <# + .SYNOPSIS + Create a new virtual environment for the specified package + .PARAMETER PackageName + The name of the package + #> + param( + [Parameter(Mandatory=$true)] + [string] $PackageName + ) + + $VenvName = Get-VenvName -PackageName $PackageName + $VenvPath = Join-Path (Get-Location) $VenvName + + Write-Host "Creating virtual environment '$VenvName' at '$VenvPath'..." + + # Create virtual environment if it doesn't exist + if (-not (Test-Path $VenvPath)) { + python -m venv $VenvPath + Write-Host "Virtual environment '$VenvName' created successfully." + } else { + Write-Host "Virtual environment '$VenvName' already exists." + } + + # Activate the environment + Use-Venv -VenvName $VenvName + + # Upgrade pip + Write-Host "Upgrading pip..." + python -m pip install --upgrade pip setuptools wheel +} + +function Use-Venv { + <# + .SYNOPSIS + Activate the specified virtual environment + .PARAMETER VenvName + The name of the virtual environment to activate + #> + param( + [Parameter(Mandatory=$true)] + [string] $VenvName + ) + + $VenvPath = Join-Path (Get-Location) $VenvName + $ActivateScript = Join-Path $VenvPath "Scripts\Activate.ps1" + + if (Test-Path $ActivateScript) { + Write-Host "Activating virtual environment '$VenvName'..." + # Ensure prompt function exists to avoid activation errors + if (-not (Get-Command prompt -ErrorAction SilentlyContinue)) { + function prompt { "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " } + } + & $ActivateScript + } else { + Write-Warning "Virtual environment '$VenvName' not found at '$VenvPath'. Trying to use current Python environment." + } +} + +function Install-PackageInVenv { + <# + .SYNOPSIS + Install a package in the current virtual environment + .PARAMETER PackageName + The name of the package to install + .PARAMETER FromSource + Whether to install from source (editable install) + #> + param( + [Parameter(Mandatory=$true)] + [string] $PackageName, + + [Parameter(Mandatory=$false)] + [bool] $FromSource = $false + ) + + if ($FromSource) { + Write-Host "Installing '$PackageName' from source (editable install)..." + pip install -e ".$PackageName" + } else { + Write-Host "Installing '$PackageName' from PyPI..." + pip install $PackageName + } +} + +# Export functions +Export-ModuleMember -Function Enable-Venv, Get-VenvName, New-VenvForPackage, Use-Venv, Install-PackageInVenv \ No newline at end of file