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

Add method for saving PLC project as a library #4

Merged
merged 28 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4e679b5
refactor: Using system-under-test naming for clarity
ahuca Dec 4, 2023
816f7b1
chore: relocated `TestTwincatProject`
ahuca Dec 4, 2023
e54ea97
feat: Added `LoadSolution` to `AutomationInterface`
ahuca Dec 4, 2023
1beed13
feat: Added `TwinGet.Utils`
ahuca Dec 6, 2023
0212bcb
test: Added more test setup to `AutomationInterfaceTests`
ahuca Dec 6, 2023
af86521
refactor: Adjusted exception type for `ThrowIfInvalidSolutionPath`
ahuca Dec 6, 2023
bfea603
chore: Added configuration parameter to build script
ahuca Dec 8, 2023
2351be6
test: Refactored `AutomationInterfaceTests`
ahuca Dec 8, 2023
8a5c44a
feat: Added `TwincatProject` as a wrapper class for a TwinCAT project
ahuca Dec 8, 2023
c722479
feat: Added file-sharing option to `CopyDirectory()`
ahuca Dec 9, 2023
46b45fc
feat: Added `ProjectFilesDeserialization`
ahuca Dec 9, 2023
2712457
feat: Added `TwincatProject` and `PlcProject`
ahuca Dec 10, 2023
54c002b
test: Added tests for `ProjectFileDeserialization`
ahuca Dec 11, 2023
d5fbf7c
refactor: Renamed files under `ProjectFileDeserialization`
ahuca Dec 11, 2023
fe7eff6
Merge remote-tracking branch 'origin/main' into 3-add-method-for-savi…
ahuca Dec 11, 2023
aad6017
test: Renamed some tests to match the name changes in source code
ahuca Dec 11, 2023
6a62d42
refactor: Extracted class `TwincatDteProvider`
ahuca Dec 11, 2023
5517ff7
test: Rearranged order of using for `TestProject`
ahuca Dec 11, 2023
5c5b46e
ci: Added explicit configuration to build step
ahuca Dec 11, 2023
2d56af7
refactor: Extracted `TryCleanUpDteProvider` method
ahuca Dec 12, 2023
8473ef1
ci: Added logger for dotnet test
ahuca Dec 12, 2023
efe446d
ci: Rearranged workflow and added `whoami`
ahuca Dec 12, 2023
7f51c1c
ci: Separated build and test steps
ahuca Dec 12, 2023
42f1bc3
feat: Added more properties to `PlcProject`
ahuca Dec 12, 2023
9812197
docs: Updated information regarding self-hosted runner configuration
ahuca Dec 12, 2023
a14a354
refactor: Changed return type for `TryCleanUpDteProvider`
ahuca Dec 13, 2023
cee99d6
ci: Refactored build script and added `common.ps1`
ahuca Dec 13, 2023
de23c1a
test: Refactored PLC project initialization in `TestTwincatProject`
ahuca Feb 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ jobs:
name: Build project
runs-on: [self-hosted, Windows, x64, TwinCAT]
steps:
- uses: actions/checkout@v3
- run: .\build.ps1
- run: dotnet test --configuration Release --no-build .\TwinGet.sln
- name: Checkout source
uses: actions/checkout@v3

- name: Build
shell: pwsh
run: |
.\build.ps1 -Configuration Release

- name: Run tests
shell: pwsh
run: |
dotnet test --configuration Release --no-build --logger "console;verbosity=detailed" .\TwinGet.sln
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ This project CI pipeline uses self-hosted runner. Following are the required sof
* [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
* [GitHub runner](https://github.com/actions/runner/releases)
* A service account for running GitHub runner as a service
* The service account must be added to the group "Distributed COM Users", for example, using the following command,
* The service account must be added to the "Administrators" group, for example, using the following command,

```powershell
Add-LocalGroupMember -Group "Distributed COM Users" -Member <windows_account_name>
Add-LocalGroupMember -Group "Administrators" -Member <windows_account_name>
```
59 changes: 0 additions & 59 deletions TestTwincatProject.sln

This file was deleted.

16 changes: 15 additions & 1 deletion TwinGet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TwinGet.AutomationInterface
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BDB6B966-4B82-4F51-9487-91CD520BC5AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwinGet.AutomationInterface.Test", "test\TwinGet.AutomationInterface.Test\TwinGet.AutomationInterface.Test.csproj", "{0A952D6F-9D6D-45B0-8535-E10ED8029B6B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TwinGet.AutomationInterface.Test", "test\TwinGet.AutomationInterface.Test\TwinGet.AutomationInterface.Test.csproj", "{0A952D6F-9D6D-45B0-8535-E10ED8029B6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwinGet.Utils", "src\TwinGet.Utils\TwinGet.Utils.csproj", "{C29610F6-4B71-49AD-B57F-784467C1CF1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwinGet.Utils.Test", "test\TwinGet.Utils.Test\TwinGet.Utils.Test.csproj", "{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -37,6 +41,14 @@ Global
{0A952D6F-9D6D-45B0-8535-E10ED8029B6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A952D6F-9D6D-45B0-8535-E10ED8029B6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A952D6F-9D6D-45B0-8535-E10ED8029B6B}.Release|Any CPU.Build.0 = Release|Any CPU
{C29610F6-4B71-49AD-B57F-784467C1CF1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C29610F6-4B71-49AD-B57F-784467C1CF1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C29610F6-4B71-49AD-B57F-784467C1CF1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C29610F6-4B71-49AD-B57F-784467C1CF1F}.Release|Any CPU.Build.0 = Release|Any CPU
{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -46,6 +58,8 @@ Global
{9B7F1AC0-7368-4504-8B3F-D9C518B85653} = {46CA5D70-7812-4809-8B40-31392DE18AA1}
{3FD0F366-6A8B-4CF8-A11E-D62D8A687EC7} = {46CA5D70-7812-4809-8B40-31392DE18AA1}
{0A952D6F-9D6D-45B0-8535-E10ED8029B6B} = {BDB6B966-4B82-4F51-9487-91CD520BC5AE}
{C29610F6-4B71-49AD-B57F-784467C1CF1F} = {46CA5D70-7812-4809-8B40-31392DE18AA1}
{1D55596F-6184-4DA6-9DFE-BDB9B358EA4B} = {BDB6B966-4B82-4F51-9487-91CD520BC5AE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {42610935-EABE-41BA-A9A9-57FB7A657F65}
Expand Down
20 changes: 15 additions & 5 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
$vsInstallationPath = .\tools\vswhere.exe -latest -property installationPath
[CmdletBinding()]
param (
[Parameter()]
[string]
[ValidateSet("Release", "Debug")]
$Configuration = "Debug"
)

$msBuildPath = Join-Path -Path $vsInstallationPath -ChildPath 'MSBuild\Current\Bin\MSBuild.exe'
. "$PSScriptRoot\common.ps1"

$null = Test-Path $msBuildPath -ErrorAction Stop
$msBuildPath = Resolve-MsBuildPath -ErrorAction Stop

if (!$msBuildPath) {
throw "Could not resolve MSBuild path."
}

$null = Get-Command nuget -ErrorAction Stop
$null = Test-Path $msBuildPath -ErrorAction Stop

$solution = Join-Path -Path $PSScriptRoot -ChildPath 'TwinGet.sln'

dotnet restore $solution
& $msBuildPath $solution -p:Configuration=Release
& $msBuildPath $solution -p:Configuration=$Configuration
15 changes: 15 additions & 0 deletions common.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
$VsWherePath = "$PSScriptRoot\tools\vswhere.exe"
$VsInstallationPath = & $VsWherePath -latest -property installationPath

function Resolve-MsBuildPath {
[CmdletBinding()]
param (
)

if (!$VsInstallationPath) {
Write-Error "Could not resolve Visual Studio installation path."
return $null
}

return Join-Path -Path $VsInstallationPath -ChildPath 'MSBuild\Current\Bin\MSBuild.exe'
}
99 changes: 51 additions & 48 deletions src/TwinGet.AutomationInterface/AutomationInterface.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
// This file is licensed to you under MIT license.

using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using TwinGet.AutomationInterface.ComMessageFilter;
using TwinGet.AutomationInterface.Exceptions;
using TwinGet.AutomationInterface.Utils;
using static TwinGet.AutomationInterface.AutomationInterfaceConstants;

namespace TwinGet.AutomationInterface
{
[SupportedOSPlatform("windows")]
public class AutomationInterface : IDisposable
{
public string ProgId { get => _progId; }
private string _progId;
private readonly EnvDTE80.DTE2? _dte;
private bool _disposedValue;
private readonly TwincatDteProvider _dteProvider;
private EnvDTE80.DTE2 _dte { get => _dteProvider.Dte; }
private EnvDTE.Solution? _solution;
private EnvDTE.SolutionBuild? _solutionBuild;
private readonly List<TwincatProject> _twincatProjects = new();

public string ProgId { get => _dteProvider.ProgId; }
public bool IsSolutionOpen { get => _solution?.IsOpen ?? false; }
public string LoadedSolutionFile { get => _solution?.FileName ?? string.Empty; }
public IReadOnlyList<TwincatProject> TwincatProjects { get => _twincatProjects; }

public AutomationInterface()
{
MessageFilter.Register();
_dte = TryInitializeDte(out _progId);
ThrowIfFailToInitializeDte(_dte);
_dteProvider = new(this, true);
}

protected bool TryCleanUpDteProvider()
{
if (_dteProvider is null) { return false; }

/// We only dispose the <see cref="_dteProvider"/> that we created.
if (_dteProvider.Owner == this)
{
_dteProvider.Dispose();
return true;
}

return false;
}

protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
;
}
if (disposing) { }

CleanUp();
TryCleanUpDteProvider();
_disposedValue = true;
}
}
Expand All @@ -48,57 +65,43 @@ public void Dispose()
GC.SuppressFinalize(this);
}

private void CleanUp()
private void ThrowIfDteIsNull()
{
_progId = string.Empty;

if (_dte is not null)
{
_dte.Quit();
Marshal.ReleaseComObject(_dte);
MessageFilter.Revoke();
}
if (_dte is null) { throw new DteInstanceIsNullException($"No {nameof(EnvDTE80.DTE2)} instance available."); }
}

private void ThrowIfFailToInitializeDte(EnvDTE80.DTE2? dte)
private static void ThrowSolutionPathNotFound(string solutionPath)
{
if (dte is null)
{
throw new CouldNotCreateTwinCatDte($"Failed to create a DTE instance due to missing TwinCAT XAE or TwinCAT-intergrated Visual Studio installation. TwinCAT can be downloaded from: {AutomationInterfaceConstants.TwincatXaeDownloadUrl}");
}
throw new FileNotFoundException($"Provided solution path does not exists.", solutionPath);
}

/// <summary>
/// Try to create a Visual Studio (or TwinCAT XAE) DTE instance.
/// </summary>
/// <param name="progId">The ProgId of the created instance if successful, empty string if not.</param>
/// <returns>The created DTE instance if successful, null if not.</returns>
private static EnvDTE80.DTE2? TryInitializeDte(out string progId)
public void LoadSolution(string filePath)
{
foreach (string p in AutomationInterfaceConstants.SupportedProgIds)
{
Type? t = Type.GetTypeFromProgID(p);
ArgumentException.ThrowIfNullOrEmpty(filePath, nameof(filePath));
if (!Path.Exists(filePath)) { ThrowSolutionPathNotFound(filePath); }

if (t is null) { continue; }
ThrowIfDteIsNull();

EnvDTE80.DTE2? dte;
try
{
dte = (EnvDTE80.DTE2?)Activator.CreateInstance(t);
if (dte is null) { continue; }
}
catch { continue; }
filePath = Path.GetFullPath(filePath);
_solution = _dte.Solution;
_solutionBuild = _dte.Solution.SolutionBuild;
_solution.Open(filePath);

if (dte.IsTwinCatIntegrated())
// Get TwinCAT projects
for (int i = ProjectItemStartingIndex; i <= _dte.Solution.Projects.Count; i++)
{
EnvDTE.Project currentItem = _dte.Solution.Projects.Item(i);

if (currentItem.IsTwincatProject())
{
progId = p;
return dte;
_twincatProjects.Add(new TwincatProject(currentItem));
}
}

progId = string.Empty;
return null;
}

public static void SaveProjectAsLibrary(string plcProjectName, string outFile, string solutionPath = "")
{

}
}
}
20 changes: 13 additions & 7 deletions src/TwinGet.AutomationInterface/AutomationInterfaceConstants.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
// This file is licensed to you under MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TwinGet.AutomationInterface
{
public static class AutomationInterfaceConstants
Expand All @@ -16,6 +10,18 @@ public static class AutomationInterfaceConstants
"VisualStudio.DTE.14.0",
"VisualStudio.DTE.15.0"
};
public static string TwincatXaeDownloadUrl = @"https://www.beckhoff.com/en-en/support/download-finder/search-result/?search=eXtended%20Automation%20Engineering%20%28XAE%29";

public const string TwincatXaeDownloadUrl = @"https://www.beckhoff.com/en-en/support/download-finder/search-result/?search=eXtended%20Automation%20Engineering%20%28XAE%29";
public const string TwincatXaeProjectKind = "{B1E792BE-AA5F-4E3C-8C82-674BF9C0715B}";
public const string TwincatPlcProjectKind = TwincatXaeProjectKind;
public const int ProjectItemStartingIndex = 1;
public const string TwincatXaeProjectExtension = ".tsproj";
public const string TwincatPlcProjectExtension = ".tspproj";
public static readonly IReadOnlyList<string> TwincatProjectExtensions = new List<string>
{
TwincatXaeProjectExtension,
TwincatPlcProjectExtension
};
public const string TwincatPlcLibraryExtension = ".library";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file is licensed to you under MIT license.

namespace TwinGet.AutomationInterface.Exceptions
{
[Serializable]
public class CouldNotCreateTwincatDteException : Exception
ahuca marked this conversation as resolved.
Show resolved Hide resolved
{
public CouldNotCreateTwincatDteException()
{
}

public CouldNotCreateTwincatDteException(string messsage) : base(messsage)
{
}

public CouldNotCreateTwincatDteException(string message, Exception inner) : base(message, inner)
{
}
}
}
Loading
Loading