checkpoint

This commit is contained in:
2020-04-29 23:47:06 -04:00
parent 64f7dd4b7d
commit 19a9fc06ba
31 changed files with 678 additions and 360 deletions

View File

@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RIDER_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$USER_HOME$/.nuget/packages/microsoft.net.test.sdk/16.5.0/build/netcoreapp2.1" />
<content url="file://$USER_HOME$/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.reporters.netcoreapp10.dll" />
<content url="file://$USER_HOME$/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.utility.netcoreapp10.dll" />
<content url="file://$USER_HOME$/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.visualstudio.dotnetcore.testadapter.dll" />
<content url="file://$MODULE_DIR$/../.." />
<orderEntry type="sourceFolder" forTests="false" />
</component>

View File

@@ -0,0 +1,32 @@
using System;
using System.Linq;
using Foodsoft.Alpm;
using Xunit;
namespace Alpm.Tests
{
public class AlpmIntegrationTest : IDisposable
{
private readonly Handle _handle = new Handle("/", "/var/lib/pacman");
[Fact]
public void TestLocalDB()
{
using var localDB = _handle.LocalDB;
Assert.Equal(15, localDB.PackageCache.Count());
}
[Fact]
public void VersionCompareTest()
{
Assert.True(Package.VersionCompare("1.0-2", "2.0-1") < 0);
Assert.True(Package.VersionCompare("1:1.0-2", "2.0-1") > 0);
Assert.Equal(0, Package.VersionCompare("2.0.2-2", "2.0.2-2"));
}
public void Dispose()
{
_handle?.Dispose();
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Foodsoft.Alpm
var err = f();
if (err != 0)
{
throw new Exception(alpm.alpm_errno(h));
throw new AlpmException(h);
}
}
@@ -24,7 +24,7 @@ namespace Foodsoft.Alpm
var err = f();
if (err < 0)
{
throw new Exception(alpm.alpm_errno(h));
throw new AlpmException(h);
}
return err == 0;
}

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Module</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>

18
Alpm/AlpmException.cs Normal file
View File

@@ -0,0 +1,18 @@
using System;
using System.Runtime.Serialization;
using Foodsoft.Alpm;
namespace Foodsoft.Alpm
{
[Serializable()]
public class AlpmException : System.Exception
{
public AlpmException() { }
internal AlpmException(SafeAlpmHandle handle) : base(alpm.alpm_strerror(alpm.alpm_errno(handle))) { }
public AlpmException(ErrNo errno) : base(alpm.alpm_strerror(errno)) { }
public AlpmException(ErrNo errno, System.Exception inner) : base(alpm.alpm_strerror(errno), inner) { }
protected AlpmException(SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
}

View File

@@ -13,7 +13,7 @@ namespace Foodsoft.Alpm
public void SetInstallReason(InstallReason reason)
{
if (alpm.alpm_pkg_set_reason(Handle, reason) != 0)
throw new Exception(alpm.alpm_errno(((SafeCachePackageHandle) Handle).SafeAlpmHandle));
throw new AlpmException(((SafeCachePackageHandle) Handle).SafeAlpmHandle);
}
}
}

View File

@@ -1,131 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
// FIXME: This is increasing the ref-count on AlpmHandle all the time, so this is NOT correct for database handles,
// or other handles that have a dispose that is not a no-op.
namespace Foodsoft.Alpm
{
internal class CollectionWrapper<TImpl, TElement> : ICollection<TElement> where TImpl : struct,
IItemsAccessor<TElement>
{
private TImpl _impl;
public CollectionWrapper(TImpl impl)
{
_impl = impl;
}
private static unsafe IntPtr ListNext(IntPtr list) => ((alpm_list_t*) list)->next;
private static unsafe IntPtr ListData(IntPtr list) => ((alpm_list_t*) list)->data;
public IEnumerator<TElement> GetEnumerator()
{
var safeAlpmHandle = _impl.SafeAlpmHandle;
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
safeAlpmHandle.DangerousAddRef(ref release);
if (!release) throw new ObjectDisposedException(_impl.GetType().FullName);
for (var list = _impl.GetItems(); list != IntPtr.Zero; list = ListNext(list))
{
yield return _impl.PtrToItem(ListData(list));
}
}
finally
{
if (release)
safeAlpmHandle.DangerousRelease();
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(TElement item)
{
_impl.AddItem(item);
}
public void Clear()
{
_impl.SetItems(IntPtr.Zero);
}
public bool Contains(TElement item)
{
var safeAlpmHandle = _impl.SafeAlpmHandle;
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
safeAlpmHandle.DangerousAddRef(ref release);
return _impl.FindItem(item) != IntPtr.Zero;
}
finally
{
if (release)
safeAlpmHandle.DangerousRelease();
}
}
public void CopyTo(TElement[] array, int arrayIndex)
{
var safeAlpmHandle = _impl.SafeAlpmHandle;
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
safeAlpmHandle.DangerousAddRef(ref release);
var intPtr = _impl.GetItems();
unsafe
{
for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list))
{
array[arrayIndex++] = _impl.PtrToItem(list->data);
}
}
}
finally
{
if (release)
safeAlpmHandle.DangerousRelease();
}
}
public bool Remove(TElement item)
{
var err = _impl.RemoveItem(item);
if (err < 0)
{
throw new Exception(alpm.alpm_errno(_impl.SafeAlpmHandle));
}
return err == 0;
}
public int Count
{
get
{
var safeAlpmHandle = _impl.SafeAlpmHandle;
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
safeAlpmHandle.DangerousAddRef(ref release);
return (int) alpm.alpm_list_count(_impl.GetItems());
}
finally
{
if (release)
safeAlpmHandle.DangerousRelease();
}
}
}
public bool IsReadOnly => false;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
@@ -10,44 +11,54 @@ namespace Foodsoft.Alpm
internal Database(SafeDatabaseHandle handle) => _handle = handle;
public void Unregister()
public void Unregister() => _handle.Close();
public void AddServer(string url) =>
API.WrapError(_handle.SafeAlpmHandle, () => alpm.alpm_db_add_server(_handle, url));
public bool RemoveServer(string url) =>
API.WrapErrorBool(_handle.SafeAlpmHandle, () => alpm.alpm_db_remove_server(_handle, url));
private readonly struct ServersImpl : ICollectionImpl<string>
{
try
{
API.WrapError(_handle.SafeAlpmHandle, () => alpm.alpm_db_unregister(_handle));
}
finally
{
_handle.Close();
}
}
private readonly struct ServersAccessor : IItemsReader<string, SafeDatabaseHandle>
{
internal ServersAccessor(SafeDatabaseHandle handle)
{
Handle = handle;
}
public SafeDatabaseHandle Handle { get; }
public IntPtr GetItems() => alpm.alpm_db_get_servers(Handle);
private readonly SafeDatabaseHandle _handle;
internal ServersImpl(SafeDatabaseHandle handle) => _handle = handle;
public SafeHandle Handle => _handle;
public IntPtr GetItems() => alpm.alpm_db_get_servers(_handle);
public string PtrToItem(IntPtr p) => Marshal.PtrToStringUTF8(p)!;
}
public void AddServer(string url) => API.WrapError(_handle.SafeAlpmHandle, () => alpm.alpm_db_add_server(_handle, url));
public bool RemoveServer(string url) => API.WrapErrorBool(_handle.SafeAlpmHandle, () => alpm.alpm_db_remove_server(_handle, url));
// FIXME: This is "bug correct", but probably dumb to do it this way, instead just call add_server
// like all the stuff in handle.
public IEnumerable<string> Servers
{
get => new EnumerableWrapper<ServersAccessor, string, SafeDatabaseHandle>(new ServersAccessor(_handle));
get => EnumerableWrapper<string>.Create(new ServersImpl(_handle));
set
{
foreach (var s in value)
var listPtr = IntPtr.Zero;
var success = false;
var err = 0;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
API.WrapError(_handle.SafeAlpmHandle, () => alpm.alpm_db_add_server(_handle, s));
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var s in value)
{
if (alpm.alpm_list_append_strdup(ref listPtr, s) == IntPtr.Zero)
throw new AlpmException(ErrNo.ERR_MEMORY);
}
success = true;
}
finally
{
if (success)
err = alpm.alpm_db_set_servers(_handle, listPtr);
else
alpm.alpm_list_free(listPtr);
}
if (err != 0)
throw new AlpmException(_handle.SafeAlpmHandle);
}
}
@@ -63,7 +74,7 @@ namespace Foodsoft.Alpm
var err = alpm.alpm_db_update(force ? 1 : 0, _handle);
if (err < 0)
{
throw new Exception(_handle.SafeAlpmHandle);
throw new AlpmException(_handle.SafeAlpmHandle);
}
return err == 0;
@@ -77,7 +88,7 @@ namespace Foodsoft.Alpm
var err = alpm.alpm_errno(_handle.SafeAlpmHandle);
if (err == ErrNo.ERR_PKG_NOT_FOUND)
return null;
throw new Exception(err);
throw new AlpmException(err);
}
return new CachePackage(new SafeCachePackageHandle(pkgPtr, _handle), this);
@@ -89,8 +100,8 @@ namespace Foodsoft.Alpm
{
var listPtr = alpm.alpm_db_get_pkgcache(_handle);
if (listPtr == IntPtr.Zero)
throw new Exception(alpm.alpm_errno(_handle.SafeAlpmHandle));
return new PackageList(new SafeListHandle<SafeDatabaseHandle>(listPtr, _handle), this);
throw new AlpmException(_handle.SafeAlpmHandle);
return new PackageList(listPtr, _handle, this);
}
}

123
Alpm/EnumerableWrapperEx.cs Normal file
View File

@@ -0,0 +1,123 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal static class EnumerableWrapperEx<TElement>
{
internal static EnumerableWrapperEx<TElement, THandle> Create<THandle>(THandle handle,
Func<THandle, IntPtr> getItems,
Func<IntPtr, TElement> getElement) where THandle : SafeHandle
{
return new EnumerableWrapperEx<TElement, THandle>(handle, getItems, getElement);
}
}
internal readonly struct EnumerableWrapperEx<TElement, THandle> : ICollection<TElement> where THandle : SafeHandle
{
private readonly THandle _handle;
private readonly Func<THandle, IntPtr> _getItems;
private readonly Func<IntPtr, TElement> _getElement;
internal EnumerableWrapperEx(THandle handle, Func<THandle, IntPtr> getItems, Func<IntPtr, TElement> getElement)
{
_handle = handle;
_getItems = getItems;
_getElement = getElement;
}
public IEnumerator<TElement> GetEnumerator()
{
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
_handle.DangerousAddRef(ref release);
if (!release) throw new ObjectDisposedException(_handle.GetType().FullName);
for (var list = _getItems(_handle); list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
yield return _getElement(Wrapper.ListData(list));
}
}
finally
{
if (release)
_handle.DangerousRelease();
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count
{
get
{
var getItems = _getItems;
var handle = _handle;
return handle.UseHandle((_) => (int) alpm.alpm_list_count(getItems(handle)));
}
}
public bool Contains(TElement item)
{
var getItems = _getItems;
var getElement = _getElement;
var handle = _handle;
switch (item)
{
case string s:
return handle.UseHandle((_) => alpm.alpm_list_find_str(getItems(handle), s) != (IntPtr.Zero));
default:
var comparer = EqualityComparer<TElement>.Default;
return handle.UseHandle((_) =>
{
for (var list = getItems(handle); list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
if (comparer.Equals(item, getElement(Wrapper.ListData(list))))
return true;
}
return false;
});
}
}
public void CopyTo(TElement[] array, int arrayIndex)
{
var getItems = _getItems;
var getElement = _getElement;
var handle = _handle;
handle.UseHandle((_) =>
{
for (var list = getItems(handle); list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
array[arrayIndex++] = getElement(Wrapper.ListData(list));
}
return arrayIndex;
});
}
public void Add(TElement item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Remove(TElement item)
{
throw new NotSupportedException();
}
public bool IsReadOnly => true;
}
}

View File

@@ -1,17 +1,31 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal struct EnumerableWrapper<TImpl, TElement, THandle> : IEnumerable<TElement> where TImpl : struct,
IItemsReader<TElement, THandle>
where THandle : SafeHandle
internal static class EnumerableWrapper<TElement>
{
internal static EnumerableWrapper<TElement, TImpl> Create<TImpl>(TImpl impl)
where TImpl : struct, ICollectionImpl<TElement>
{
return new EnumerableWrapper<TElement, TImpl>(impl);
}
}
internal struct EnumerableWrapper<TElement, TImpl> : ICollection<TElement> where TImpl : struct,
ICollectionImpl<TElement>
{
private TImpl _impl;
public static EnumerableWrapper<TElement, TImpl> Create(TImpl impl)
{
return new EnumerableWrapper<TElement, TImpl>(impl);
}
public EnumerableWrapper(TImpl impl)
{
_impl = impl;
@@ -40,5 +54,67 @@ namespace Foodsoft.Alpm
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count
{
get
{
var impl = _impl;
return impl.Handle.UseHandle((_) => (int) alpm.alpm_list_count(impl.GetItems()));
}
}
public bool Contains(TElement item)
{
var impl = _impl;
switch (item)
{
case string s:
return impl.Handle.UseHandle((_) => alpm.alpm_list_find_str(impl.GetItems(), s) != (IntPtr.Zero));
default:
var comparer = EqualityComparer<TElement>.Default;
return _impl.Handle.UseHandle((_) =>
{
for (var list = impl.GetItems(); list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
if (comparer.Equals(item, impl.PtrToItem(Wrapper.ListData(list))))
return true;
}
return false;
});
}
}
public void CopyTo(TElement[] array, int arrayIndex)
{
var impl = _impl;
impl.Handle.UseHandle((_) =>
{
for (var list = impl.GetItems(); list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
array[arrayIndex++] = impl.PtrToItem(Wrapper.ListData(list));
}
return arrayIndex;
});
}
public void Add(TElement item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Remove(TElement item)
{
throw new NotSupportedException();
}
public bool IsReadOnly => true;
}
}

19
Alpm/Enums.cs Normal file
View File

@@ -0,0 +1,19 @@
using System;
namespace Foodsoft.Alpm
{
[Flags]
public enum SigLevel
{
Package = (1 << 0),
PackageOptional = (1 << 1),
PackageMarginalOK = (1 << 2),
PackageUnknownOK = (1 << 3),
Database = (1 << 10),
DatabaseOptional = (1 << 11),
DatabaseMarginalOK = (1 << 12),
DatabaseUnknownOK = (1 << 13),
UseDefault = (1 << 31)
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Runtime.Serialization;
using Foodsoft.Alpm;
namespace Foodsoft.Alpm
{
[Serializable()]
public class Exception : System.Exception
{
public Exception() { }
internal Exception(SafeAlpmHandle handle) : base(alpm.alpm_strerror(alpm.alpm_errno(handle))) { }
public Exception(ErrNo errno) : base(alpm.alpm_strerror(errno)) { }
public Exception(ErrNo errno, System.Exception inner) : base(alpm.alpm_strerror(errno), inner) { }
protected Exception(SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
}

11
Alpm/FilePackage.cs Normal file
View File

@@ -0,0 +1,11 @@
namespace Foodsoft.Alpm
{
public class FilePackage : Package
{
// ReSharper disable once SuggestBaseTypeForParameter
internal FilePackage(SafeFilePackageHandle handle) : base(handle)
{
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Foodsoft.Alpm
_handle = alpm.alpm_initialize(root, dbpath, out var err);
if (_handle.IsInvalid)
{
throw new Exception(err);
throw new AlpmException(err);
}
}
@@ -28,7 +28,7 @@ namespace Foodsoft.Alpm
// managed by the alpm handle.
if (ptr == IntPtr.Zero)
{
throw new Exception(_handle);
throw new AlpmException(_handle);
}
return new Database(new SafeDatabaseHandle(ptr, _handle));
@@ -36,40 +36,28 @@ namespace Foodsoft.Alpm
public Database LocalDB => ToDatabase(alpm.alpm_get_localdb(_handle));
public Database RegisterSyncDB(string treename, SigLevel sigLevel) =>
public Database RegisterSyncDB(string treename, SigLevel sigLevel = 0) =>
ToDatabase(alpm.alpm_register_syncdb(_handle, treename, sigLevel));
public void AddCacheDir(string dir) => API.WrapError(_handle, () => alpm.alpm_option_add_cachedir(_handle, dir));
public bool RemoveCacheDir(string dir) => API.WrapErrorBool(_handle, () => alpm.alpm_option_add_cachedir(_handle, dir));
private readonly struct CacheDirsAccessor : IItemsAccessor<string>
private readonly struct CacheDirsImpl : ICollectionImpl<string>
{
internal CacheDirsAccessor(SafeAlpmHandle safeAlpmHandle)
{
SafeAlpmHandle = safeAlpmHandle;
}
public SafeAlpmHandle SafeAlpmHandle { get; }
public IntPtr GetItems() => alpm.alpm_option_get_cachedirs(SafeAlpmHandle);
public int SetItems(IntPtr list) => alpm.alpm_option_set_cachedirs(SafeAlpmHandle, list);
public int AddItem(string item) => alpm.alpm_option_add_cachedir(SafeAlpmHandle, item);
public int RemoveItem(string item) => alpm.alpm_option_remove_cachedir(SafeAlpmHandle, item);
public IntPtr FindItem(string item) => alpm.alpm_list_find_str(GetItems(), item);
private readonly SafeAlpmHandle _handle;
internal CacheDirsImpl(SafeAlpmHandle handle) => _handle = handle;
public SafeHandle Handle => _handle;
public IntPtr GetItems() => alpm.alpm_option_get_cachedirs(_handle);
public string PtrToItem(IntPtr p) => Marshal.PtrToStringUTF8(p)!;
}
public ICollection<string> CacheDirs
{
get => new CollectionWrapper<CacheDirsAccessor, string>(new CacheDirsAccessor(_handle));
set
{
foreach (var s in value)
{
API.WrapError(_handle, () => alpm.alpm_option_add_cachedir(_handle, s));
}
}
get =>
EnumerableWrapperEx<string>.Create(_handle, alpm.alpm_option_get_cachedirs, Marshal.PtrToStringUTF8!);
set => Wrapper.SetStringCollection(value, _handle, (s) => alpm.alpm_option_add_cachedir(_handle, s));
}
public bool ShouldIgnorePackage(Package pkg)
{
return alpm.alpm_pkg_should_ignore(_handle, pkg.Handle) == 0;
}
public bool ShouldIgnorePackage(Package pkg) => alpm.alpm_pkg_should_ignore(_handle, pkg.Handle) == 0;
}
}

View File

@@ -1,15 +0,0 @@
using System;
namespace Foodsoft.Alpm
{
internal interface IItemsAccessor<TElement>
{
SafeAlpmHandle SafeAlpmHandle { get; }
public IntPtr GetItems();
public int SetItems(IntPtr list);
public int AddItem(TElement item);
public int RemoveItem(TElement item);
public IntPtr FindItem(TElement item);
public TElement PtrToItem(IntPtr p);
}
}

View File

@@ -3,9 +3,9 @@ using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal interface IItemsReader<out TElement, out THandle> where THandle : SafeHandle
internal interface ICollectionImpl<TElement>
{
public THandle Handle { get; }
public SafeHandle Handle { get; }
public IntPtr GetItems();
public TElement PtrToItem(IntPtr p);
}

View File

@@ -44,15 +44,15 @@ namespace Foodsoft.Alpm
public long Size { get; }
public long InstalledSize { get; }
public InstallReason InstallReason { get; }
public IEnumerable<string> Licenses { get; }
public IReadOnlyCollection<string> Groups { get; }
public IReadOnlyCollection<Depend> Depends { get; }
public IReadOnlyCollection<Depend> OptDepends { get; }
public IReadOnlyCollection<Depend> CheckDepends { get; }
public IReadOnlyCollection<Depend> MakeDepends { get; }
public IReadOnlyCollection<Depend> Conflicts { get; }
public IReadOnlyCollection<Depend> Provides { get; }
public IReadOnlyCollection<Depend> Replaces { get; }
public ICollection<string> Licenses { get; }
public ICollection<string> Groups { get; }
public ICollection<Depend> Depends { get; }
public ICollection<Depend> OptDepends { get; }
public ICollection<Depend> CheckDepends { get; }
public ICollection<Depend> MakeDepends { get; }
public ICollection<Depend> Conflicts { get; }
public ICollection<Depend> Provides { get; }
public ICollection<Depend> Replaces { get; }
public IReadOnlyList<File> FileList { get; }
public IReadOnlyList<Backup> Backup { get; }
public string Base64Signature { get; }

View File

@@ -80,20 +80,7 @@ namespace Foodsoft.Alpm
}
/** PGP signature verification options */
[Flags]
public enum SigLevel
{
SIG_PACKAGE = (1 << 0),
SIG_PACKAGE_OPTIONAL = (1 << 1),
SIG_PACKAGE_MARGINAL_OK = (1 << 2),
SIG_PACKAGE_UNKNOWN_OK = (1 << 3),
SIG_DATABASE = (1 << 10),
SIG_DATABASE_OPTIONAL = (1 << 11),
SIG_DATABASE_MARGINAL_OK = (1 << 12),
SIG_DATABASE_UNKNOWN_OK = (1 << 13),
SIG_USE_DEFAULT = (1 << 31)
}
/** PGP signature verification status return codes */
enum alpm_sigstatus_t

View File

@@ -31,21 +31,24 @@ namespace Foodsoft.Alpm
public long InstalledSize => alpm.alpm_pkg_get_isize(Handle);
public InstallReason InstallReason => alpm.alpm_pkg_get_reason(Handle);
public IEnumerable<string> Licenses { get; }
public IReadOnlyCollection<string> Groups { get; }
public IReadOnlyCollection<Depend> Depends { get; }
public IReadOnlyCollection<Depend> OptDepends { get; }
public IReadOnlyCollection<Depend> CheckDepends { get; }
public IReadOnlyCollection<Depend> MakeDepends { get; }
public IReadOnlyCollection<Depend> Conflicts { get; }
public IReadOnlyCollection<Depend> Provides { get; }
public IReadOnlyCollection<Depend> Replaces { get; }
public ICollection<string> Licenses =>
EnumerableWrapperEx<string>.Create(Handle, alpm.alpm_pkg_get_licenses, Marshal.PtrToStringUTF8!);
public ICollection<string> Groups { get; }
public ICollection<Depend> Depends { get; }
public ICollection<Depend> OptDepends { get; }
public ICollection<Depend> CheckDepends { get; }
public ICollection<Depend> MakeDepends { get; }
public ICollection<Depend> Conflicts { get; }
public ICollection<Depend> Provides { get; }
public ICollection<Depend> Replaces { get; }
public IReadOnlyList<File> FileList { get; }
public IReadOnlyList<Backup> Backup { get; }
public string Base64Signature { get; }
public ValidationType Validation { get; }
public bool HasScriptlet { get; }
public long DownloadSize { get; }
public bool CheckMD5Sum()
{
throw new NotImplementedException();
@@ -53,21 +56,24 @@ namespace Foodsoft.Alpm
public virtual Database? DB => null;
public IEnumerator<Package> ComputeRequiredBy()
public IEnumerator<Package> ComputeRequiredBy()
{
throw new NotImplementedException();
}
public IEnumerator<Package> ComputeOptionalFor()
public IEnumerator<Package> ComputeOptionalFor()
{
throw new NotImplementedException();
}
public void Dispose()
{
Handle.Dispose();
}
public static int VersionCompare(string v1, string v2)
{
return alpm.alpm_pkg_vercmp(v1, v2);
}
}
}

View File

@@ -2,39 +2,42 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
public struct PackageList : IEnumerable<Package>, IDisposable
public class PackageList : ICollection<CachePackage>
{
private SafeListHandle<SafeDatabaseHandle> _handle;
private Database _db;
private readonly SafeDatabaseHandle _parentHandle;
private readonly IntPtr _listPtr;
private readonly Database _db;
internal PackageList(SafeListHandle<SafeDatabaseHandle> handle, Database db)
internal PackageList(IntPtr listPtr, SafeDatabaseHandle parentHandle, Database db)
{
_handle = handle;
_parentHandle = parentHandle;
_listPtr = listPtr;
_db = db;
}
public IEnumerator<Package> GetEnumerator()
public IEnumerator<CachePackage> GetEnumerator()
{
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
_handle.DangerousAddRef(ref release);
if (!release) throw new ObjectDisposedException(_handle.GetType().FullName);
for (var list = _handle.DangerousGetHandle(); list != IntPtr.Zero; list = Wrapper.ListNext(list))
_parentHandle.DangerousAddRef(ref release);
if (!release) throw new ObjectDisposedException(_parentHandle.GetType().FullName);
for (var list = _listPtr; list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
yield return new CachePackage(
new SafeCachePackageHandle(Wrapper.ListData(list), _handle.ParentHandle), _db);
new SafeCachePackageHandle(Wrapper.ListData(list), _parentHandle), _db);
}
}
finally
{
if (release)
_handle.DangerousRelease();
_parentHandle.DangerousRelease();
}
}
@@ -42,10 +45,51 @@ namespace Foodsoft.Alpm
{
return GetEnumerator();
}
public void Dispose()
public bool Contains(CachePackage item)
{
_handle.Dispose();
var name = item.Name;
return _parentHandle.UseHandle(_listPtr, (list) =>
{
for (; list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
if (name == alpm.alpm_pkg_get_name(list))
return true;
}
return false;
});
}
public void CopyTo(CachePackage[] array, int arrayIndex)
{
_parentHandle.UseHandle(_listPtr,(list) =>
{
for (; list != IntPtr.Zero; list = Wrapper.ListNext(list))
{
array[arrayIndex++] = new CachePackage(
new SafeCachePackageHandle(Wrapper.ListData(list), _parentHandle), _db);
}
return 0;
});
}
public void Add(CachePackage item)
{
throw new NotSupportedException();
}
public bool Remove(CachePackage item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public int Count => _parentHandle.UseHandle(_listPtr,(list) => (int) alpm.alpm_list_count(list));
public bool IsReadOnly => true;
}
}

View File

@@ -1,17 +1,29 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal sealed class SafeCachePackageHandle : SafePackageHandle
{
internal SafeAlpmHandle SafeAlpmHandle => SafeDatabaseHandle.SafeAlpmHandle;
internal SafeDatabaseHandle SafeDatabaseHandle { get; } = null!;
internal SafeAlpmHandle SafeAlpmHandle
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[PrePrepareMethod]
get => SafeDatabaseHandle.SafeAlpmHandle;
}
internal SafeDatabaseHandle SafeDatabaseHandle
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[PrePrepareMethod]
get;
} = null!;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[PrePrepareMethod]
internal SafeCachePackageHandle(IntPtr ptr, SafeDatabaseHandle dbHandle)
internal SafeCachePackageHandle(IntPtr ptr, SafeDatabaseHandle parentHandle)
: base()
{
var success = false;
@@ -20,10 +32,10 @@ namespace Foodsoft.Alpm
try { }
finally
{
dbHandle.DangerousAddRef(ref success);
parentHandle.DangerousAddRef(ref success);
if (success)
{
SafeDatabaseHandle = dbHandle;
SafeDatabaseHandle = parentHandle;
handle = ptr;
}
}
@@ -36,11 +48,5 @@ namespace Foodsoft.Alpm
SafeDatabaseHandle.DangerousRelease();
return true;
}
public override bool IsInvalid
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), PrePrepareMethod]
get => handle == IntPtr.Zero;
}
}
}

View File

@@ -12,12 +12,11 @@ namespace Foodsoft.Alpm
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[PrePrepareMethod]
get;
} = null!; // we always throw in the constructor on failure
} = null!;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[PrePrepareMethod]
internal SafeDatabaseHandle(IntPtr dbPtr, SafeAlpmHandle safeAlpmHandle)
: base(IntPtr.Zero, true)
internal SafeDatabaseHandle(IntPtr dbPtr, SafeAlpmHandle safeAlpmHandle) : base(IntPtr.Zero, true)
{
var success = false;
@@ -39,8 +38,9 @@ namespace Foodsoft.Alpm
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), PrePrepareMethod]
protected override bool ReleaseHandle()
{
var err = alpm.alpm_db_unregister(handle);
SafeAlpmHandle.DangerousRelease();
return true;
return err == 0;
}
public override bool IsInvalid

View File

@@ -0,0 +1,39 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal class SafeFilePackageHandle : SafePackageHandle
{
internal SafeAlpmHandle SafeAlpmHandle { get; } = null!;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[PrePrepareMethod]
internal SafeFilePackageHandle(IntPtr ptr, SafeAlpmHandle parentHandle)
{
var success = false;
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
parentHandle.DangerousAddRef(ref success);
if (success)
{
SafeAlpmHandle = parentHandle;
handle = ptr;
}
}
if (!success) throw new ObjectDisposedException(GetType().FullName);
}
protected override bool ReleaseHandle()
{
SafeAlpmHandle.DangerousRelease();
return true;
}
}
}

View File

@@ -4,13 +4,9 @@ using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
internal class SafePackageHandle : SafeHandle
internal abstract class SafePackageHandle : SafeHandle
{
protected SafePackageHandle() : base(IntPtr.Zero, true) { }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), PrePrepareMethod]
protected override bool ReleaseHandle() => alpm.alpm_pkg_free(handle) == 0;
public override bool IsInvalid
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), PrePrepareMethod]

View File

@@ -14,30 +14,14 @@ namespace Foodsoft.Alpm
{
if (pNativeData == IntPtr.Zero)
return;
Marshal.FreeHGlobal(pNativeData);
Marshal.FreeCoTaskMem(pNativeData);
}
public int GetNativeDataSize() => -1;
public unsafe IntPtr MarshalManagedToNative(object? managedObj)
public IntPtr MarshalManagedToNative(object? managedObj)
{
if (managedObj is null) return IntPtr.Zero;
var s = (string) managedObj;
var nb = Encoding.UTF8.GetMaxByteCount(s.Length);
var pMem = Marshal.AllocHGlobal(nb + 1);
var pbMem = (byte*) pMem;
int nbWritten;
fixed (char* firstChar = s)
{
nbWritten = Encoding.UTF8.GetBytes(firstChar, s.Length, pbMem, nb);
}
pbMem[nbWritten] = 0;
return pMem;
return Marshal.StringToCoTaskMemUTF8((string?) managedObj);
}
public object MarshalNativeToManaged(IntPtr pNativeData)

View File

@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Foodsoft.Alpm
{
@@ -6,5 +10,37 @@ namespace Foodsoft.Alpm
{
internal static unsafe IntPtr ListNext(IntPtr list) => ((alpm_list_t*) list)->next;
internal static unsafe IntPtr ListData(IntPtr list) => ((alpm_list_t*) list)->data;
internal static TResult UseHandle<TResult>(this SafeHandle handle, IntPtr ptr, Func<IntPtr, TResult> op)
{
var release = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
handle.DangerousAddRef(ref release);
if (!release) throw new ObjectDisposedException(handle.GetType().FullName);
return op(ptr);
}
finally
{
if (release)
handle.DangerousRelease();
}
}
internal static TResult UseHandle<TResult>(this SafeHandle handle, Func<IntPtr, TResult> op) =>
handle.UseHandle(handle.DangerousGetHandle(), op);
internal static void SetStringCollection(IEnumerable<string> value, SafeAlpmHandle safeAlpmHandle, Func<string, int> op)
{
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var s in value)
{
if (op(s) < 0)
throw new AlpmException(safeAlpmHandle);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Foodsoft.Alpm;
@@ -31,7 +32,11 @@ namespace Foodsoft.Alpm
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
public static extern string alpm_pkg_get_name(SafePackageHandle pkg);
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
public static extern string alpm_pkg_get_name(IntPtr pkg);
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
public static extern string alpm_pkg_get_version(SafePackageHandle pkg);
@@ -79,8 +84,7 @@ namespace Foodsoft.Alpm
public static extern InstallReason alpm_pkg_get_reason(SafePackageHandle pkg);
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
public static extern string alpm_pkg_get_licenses(SafePackageHandle pkg);
public static extern IntPtr alpm_pkg_get_licenses(SafePackageHandle pkg);
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
@@ -115,8 +119,7 @@ namespace Foodsoft.Alpm
[DllImport(nameof(alpm))]
public static extern int alpm_pkg_free(IntPtr ptr);
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_option_get_cachedirs(SafeAlpmHandle handle);
@@ -124,12 +127,14 @@ namespace Foodsoft.Alpm
public static extern int alpm_option_set_cachedirs(SafeAlpmHandle handle, IntPtr cachedirs);
[DllImport(nameof(alpm))]
public static extern int alpm_option_add_cachedir(SafeAlpmHandle handle,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))] string cachedir);
public static extern int alpm_option_add_cachedir(SafeAlpmHandle handle,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))]
string cachedir);
[DllImport(nameof(alpm))]
public static extern int alpm_option_remove_cachedir(SafeAlpmHandle handle,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))] string cachedir);
public static extern int alpm_option_remove_cachedir(SafeAlpmHandle handle,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))]
string cachedir);
[DllImport(nameof(alpm))]
public static extern ErrNo alpm_errno(SafeAlpmHandle handle);
@@ -145,13 +150,23 @@ namespace Foodsoft.Alpm
public static extern unsafe alpm_list_t* alpm_option_get_syncdbs();
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_register_syncdb(SafeAlpmHandle handle, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))] string treename, SigLevel sigLevel);
public static extern IntPtr alpm_register_syncdb(SafeAlpmHandle handle,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))]
string treename, SigLevel sigLevel);
[DllImport(nameof(alpm))]
public static extern int alpm_db_unregister_all_syncdbs(SafeAlpmHandle handle);
[DllImport(nameof(alpm))]
public static extern unsafe alpm_list_t* alpm_list_next(alpm_list_t* list);
public static extern IntPtr alpm_list_add(IntPtr list, IntPtr item);
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_list_append_strdup(ref IntPtr list,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))]
string str);
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_list_next(IntPtr list);
[DllImport(nameof(alpm))]
public static extern UIntPtr alpm_list_count(IntPtr list);
@@ -159,8 +174,16 @@ namespace Foodsoft.Alpm
[DllImport(nameof(alpm))]
public static extern void alpm_list_free(IntPtr list);
// ReSharper disable once InconsistentNaming
public delegate void alpm_fn_free(IntPtr ptr);
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_list_find_str(IntPtr list, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))] string needle);
public static extern void alpm_list_free_inner(IntPtr list, alpm_fn_free freeFn);
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_list_find_str(IntPtr list,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8InMarshaler))]
string needle);
[DllImport(nameof(alpm))]
public static extern int alpm_unlock(Handle handle);
@@ -187,7 +210,7 @@ namespace Foodsoft.Alpm
string url);
[DllImport(nameof(alpm))]
public static extern int alpm_db_unregister(SafeDatabaseHandle db);
public static extern int alpm_db_unregister(IntPtr db);
[DllImport(nameof(alpm))]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8OutMarshaler))]
@@ -221,5 +244,7 @@ namespace Foodsoft.Alpm
[DllImport(nameof(alpm))]
public static extern IntPtr alpm_db_get_pkgcache(SafeDatabaseHandle db);
[DllImport(nameof(alpm))]
public static extern int alpm_pkg_vercmp(string v1, string v2);
}
}

View File

@@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alpm", "Alpm\Alpm.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{1215BE9E-B4F4-4F42-B4A2-E890C5EAA903}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alpm.Tests", "Alpm.Tests\Alpm.Tests.csproj", "{96EA63C3-18AE-4CE4-9C87-61A3CAC477AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -18,5 +20,9 @@ Global
{1215BE9E-B4F4-4F42-B4A2-E890C5EAA903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1215BE9E-B4F4-4F42-B4A2-E890C5EAA903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1215BE9E-B4F4-4F42-B4A2-E890C5EAA903}.Release|Any CPU.Build.0 = Release|Any CPU
{96EA63C3-18AE-4CE4-9C87-61A3CAC477AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96EA63C3-18AE-4CE4-9C87-61A3CAC477AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96EA63C3-18AE-4CE4-9C87-61A3CAC477AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96EA63C3-18AE-4CE4-9C87-61A3CAC477AD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,2 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=alpm/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=alpm/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Foodsoft/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=pacman/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=strdup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=syncdb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=syncdbs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=treename/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vercmp/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,39 +1,103 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Foodsoft.Alpm;
// ReSharper disable once UnusedType.Global
namespace Samples
{
class Program
[Example("installed")]
internal class Installed : IExample
{
static void Other()
public int Run(string[] args)
{
using var h = new Handle("/", "/home/luebsj/db");
using var db = h.RegisterSyncDB("jkl-repo", SigLevel.SIG_PACKAGE_OPTIONAL | SigLevel.SIG_USE_DEFAULT);
db.AddServer("http://arch.johnluebs.com/x86_64");
db.Update(false);
foreach (var a in db.Servers)
{
Console.WriteLine("Hello {0}", a);
}
using var h = new Handle("/", "/var/lib/pacman");
using var db = h.LocalDB;
var servers = db.Servers;
var b = new string[3];
var l = db.Servers.ToImmutableList();
foreach (var pkg in db.PackageCache)
using (pkg)
{
Console.WriteLine("{0} {1}", pkg.Name, pkg.Version);
}
using var cache = db.PackageCache;
foreach ( var p in cache) using(p)
{
Console.WriteLine("Hello: {0} {1} {2}", p.Name, p.Filename, p.BuildDate);
}
Console.WriteLine("Hello World! {0} {1} {2}", db.Name, API.Version,
l[0]);
return 0;
}
static void Main(string[] args)
}
[Example("search")]
internal class Search : IExample
{
public int Run(string[] args)
{
Other();
using var h = new Handle("/", "/var/lib/pacman");
using var db = h.RegisterSyncDB("core");
db.Servers = new[]{"http://www.google.com"};
db.Update(true);
foreach (var pkg in db.PackageCache)
using (pkg)
{
Console.WriteLine("{0} {1} {2}", pkg.Name, pkg.Version, pkg.Description);
}
return 0;
}
}
}
namespace Samples
{
internal static class Program
{
private static int Main(string[] args)
{
var examples = (from t in Assembly.GetExecutingAssembly().GetTypes()
let attribute = (Example?) t.GetCustomAttribute(typeof(Example))
where attribute != null
select new {Name = attribute.Name, Type = t}).ToImmutableDictionary((e) => e.Name);
if (args.Length < 1)
{
Console.Error.WriteLine("Sample");
foreach (var example in examples.Values)
{
Console.Error.WriteLine("{0}", example.Name);
}
return 1;
}
int ret;
try
{
ret = ((IExample) Activator.CreateInstance(examples[args[0]].Type)!).Run(args);
}
catch (AlpmException e)
{
Console.Error.WriteLine("{0}", e.Message);
return 1;
}
return ret;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public class Example : Attribute
{
public string Name { get; }
public Example(string name)
{
Name = name;
}
}
public interface IExample
{
public int Run(string[] args);
}
}

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>