Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ public PInvokeGeneratorConfiguration(string language, string languageStandard, s
{
throw new ArgumentOutOfRangeException(nameof(options));
}
else if ((options & PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles) == 0 && Directory.Exists(_outputLocation))
{
// In single-file mode the output location is treated as a file path; pointing it at an
// existing directory otherwise surfaces as a confusing UnauthorizedAccessException (or a
// DirectoryNotFoundException with a trailing separator) when the stream is opened.
throw new ArgumentException($"The output location '{_outputLocation}' is an existing directory, but single-file output was requested. Specify a file path for the output location, or pass '--config multi-file' to generate multiple files into the directory.", nameof(outputLocation));
}
else if ((options & PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles) != 0 && File.Exists(_outputLocation))
{
// In multi-file mode the output location is treated as a directory that each file is
// written into; pointing it at an existing file otherwise fails when that directory is
// created.
throw new ArgumentException($"The output location '{_outputLocation}' is an existing file, but multi-file output was requested. Specify a directory path for the output location, or remove '--config multi-file' to generate a single file.", nameof(outputLocation));
}

if ((options & PInvokeGeneratorConfigurationOptions.GeneratePreviewCode) != 0)
{
Expand Down
69 changes: 40 additions & 29 deletions sources/ClangSharpPInvokeGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,35 +537,46 @@ public static int Run()
translationFlags |= CXTranslationUnit_IncludeAttributedTypes; // Include attributed types in CXType
translationFlags |= CXTranslationUnit_VisitImplicitAttributes; // Implicit attributes should be visited

var config = new PInvokeGeneratorConfiguration(language, std, namespaceName, outputLocation, headerFile, outputMode, configOptions) {
DefaultClass = methodClassName,
ExcludedNames = excludedNames,
IncludedNames = includedNames,
LibraryPath = libraryPath,
MethodPrefixToStrip = methodPrefixToStrip,
NativeTypeNamesToStrip = nativeTypeNamesToStrip,
RemappedNames = remappedNames,
RemappedTypeNames = remappedTypeNames,
RemappedFieldNames = remappedFieldNames,
TraversalNames = traversalNames,
TestOutputLocation = testOutputLocation,
WithAccessSpecifiers = withAccessSpecifiers,
WithAttributes = withAttributes,
WithCallConvs = withCallConvs,
WithClasses = withClasses,
WithGuids = withGuids,
WithLengths = withLengths,
WithLibraryPaths = withLibraryPaths,
WithManualImports = withManualImports,
WithNamespaces = withNamespaces,
WithReadonlys = withReadonlys,
WithSetLastErrors = withSetLastErrors,
WithSuppressGCTransitions = withSuppressGCTransitions,
WithTransparentStructs = withTransparentStructs,
WithTypes = withTypes,
WithUsings = withUsings,
WithPackings = withPackings,
};
PInvokeGeneratorConfiguration config;

try
{
config = new PInvokeGeneratorConfiguration(language, std, namespaceName, outputLocation, headerFile, outputMode, configOptions) {
DefaultClass = methodClassName,
ExcludedNames = excludedNames,
IncludedNames = includedNames,
LibraryPath = libraryPath,
MethodPrefixToStrip = methodPrefixToStrip,
NativeTypeNamesToStrip = nativeTypeNamesToStrip,
RemappedNames = remappedNames,
RemappedTypeNames = remappedTypeNames,
RemappedFieldNames = remappedFieldNames,
TraversalNames = traversalNames,
TestOutputLocation = testOutputLocation,
WithAccessSpecifiers = withAccessSpecifiers,
WithAttributes = withAttributes,
WithCallConvs = withCallConvs,
WithClasses = withClasses,
WithGuids = withGuids,
WithLengths = withLengths,
WithLibraryPaths = withLibraryPaths,
WithManualImports = withManualImports,
WithNamespaces = withNamespaces,
WithReadonlys = withReadonlys,
WithSetLastErrors = withSetLastErrors,
WithSuppressGCTransitions = withSuppressGCTransitions,
WithTransparentStructs = withTransparentStructs,
WithTypes = withTypes,
WithUsings = withUsings,
WithPackings = withPackings,
};
}
catch (ArgumentException e)
{
Console.Error.Write($"Error: {e.Message}");
Console.Error.Write(Environment.NewLine);
return -1;
}

if (config.GenerateMacroBindings)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.

using System.IO;
using NUnit.Framework;

namespace ClangSharp.UnitTests;

public sealed class ConfigurationOutputLocationTest
{
private static PInvokeGeneratorConfiguration CreateConfiguration(string outputLocation, PInvokeGeneratorConfigurationOptions options)
{
return new PInvokeGeneratorConfiguration("c++", "c++17", "ClangSharp.Test", outputLocation, headerFile: null, PInvokeGeneratorOutputMode.CSharp, options);
}

[Test]
public void SingleFileModeRejectsExistingDirectory()
{
var directory = Directory.CreateTempSubdirectory("ClangSharpTest");

try
{
var exception = Assert.Throws<System.ArgumentException>(() => CreateConfiguration(directory.FullName, PInvokeGeneratorConfigurationOptions.None))!;
Assert.That(exception.Message, Does.Contain("existing directory"));
Assert.That(exception.Message, Does.Contain("--config multi-file"));
}
finally
{
directory.Delete(recursive: true);
}
}

[Test]
public void MultiFileModeRejectsExistingFile()
{
var file = Path.GetTempFileName();

try
{
var exception = Assert.Throws<System.ArgumentException>(() => CreateConfiguration(file, PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles))!;
Assert.That(exception.Message, Does.Contain("existing file"));
Assert.That(exception.Message, Does.Contain("--config multi-file"));
}
finally
{
File.Delete(file);
}
}

[Test]
public void SingleFileModeAllowsExistingFile()
{
var file = Path.GetTempFileName();

try
{
Assert.DoesNotThrow(() => CreateConfiguration(file, PInvokeGeneratorConfigurationOptions.None));
}
finally
{
File.Delete(file);
}
}

[Test]
public void MultiFileModeAllowsExistingDirectory()
{
var directory = Directory.CreateTempSubdirectory("ClangSharpTest");

try
{
Assert.DoesNotThrow(() => CreateConfiguration(directory.FullName, PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles));
}
finally
{
directory.Delete(recursive: true);
}
}

[Test]
public void NonExistentOutputLocationIsAllowedInEitherMode()
{
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

Assert.DoesNotThrow(() => CreateConfiguration(path, PInvokeGeneratorConfigurationOptions.None));
Assert.DoesNotThrow(() => CreateConfiguration(path, PInvokeGeneratorConfigurationOptions.GenerateMultipleFiles));
}
}
Loading