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
171 changes: 171 additions & 0 deletions tests/sharpie/Sharpie.Bind.Tests/ObjectiveCClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,175 @@ public struct MyStruct {
Assert.That (bindings.AdditionalFiles ["ApiDefinition.cs"].Trim (), Is.EqualTo (expectedApiDefinitionBindings.Trim ()), "Api definition");
Assert.That (bindings.AdditionalFiles ["StructsAndEnums.cs"].Trim (), Is.EqualTo (expectedStructAndEnumsBindings.Trim ()), "Struct and enums");
}

[Test]
public void Scope_RelativePath ()
{
// Verify that --scope with a relative path works correctly
// (the relative path should be resolved to absolute before matching).
var binder = new BindTool ();
var tmpdir = Cache.CreateTemporaryDirectory ();
var subdir = Path.Combine (tmpdir, "headers");
Directory.CreateDirectory (subdir);

// Create a header in the scoped directory
var scopedHeader = Path.Combine (subdir, "InScope.h");
File.WriteAllText (scopedHeader,
"""
@interface InScopeClass {
}
@property int Value;
@end
""");

// Create an umbrella header that includes both
var mainHeader = Path.Combine (tmpdir, "main.h");
File.WriteAllText (mainHeader, $"#import \"{scopedHeader}\"\n");

binder.SplitDocuments = false;
binder.SourceFile = mainHeader;
binder.OutputDirectory = tmpdir;

// Use a relative scope path (the bug was that this produced empty output)
var oldCwd = Environment.CurrentDirectory;
try {
Environment.CurrentDirectory = tmpdir;
binder.DirectoriesInScope.Add (Path.GetFullPath ("headers"));
} finally {
Environment.CurrentDirectory = oldCwd;
}

Configuration.IgnoreIfIgnoredPlatform (binder.Platform);
binder.PlatformAssembly = Extensions.GetPlatformAssemblyPath (binder.Platform);
binder.ClangResourceDirectory = Extensions.GetClangResourceDirectory ();
var bindings = binder.BindInOrOut ();
var expectedBindings =
"""
using Foundation;

// @interface InScopeClass
interface InScopeClass {
// @property int Value;
[Export ("Value")]
int Value { get; set; }
}

""";
bindings.AssertSuccess (expectedBindings);
}

[Test]
public void Scope_FiltersOutOfScopeDeclarations ()
{
// Verify that declarations from headers outside the scope directory are not bound.
var binder = new BindTool ();
var tmpdir = Cache.CreateTemporaryDirectory ();
var scopedDir = Path.Combine (tmpdir, "scoped");
var unscopedDir = Path.Combine (tmpdir, "unscoped");
Directory.CreateDirectory (scopedDir);
Directory.CreateDirectory (unscopedDir);

var scopedHeader = Path.Combine (scopedDir, "Scoped.h");
File.WriteAllText (scopedHeader,
"""
@interface ScopedClass {
}
@property int A;
@end
""");

var unscopedHeader = Path.Combine (unscopedDir, "Unscoped.h");
File.WriteAllText (unscopedHeader,
"""
@interface UnscopedClass {
}
@property int B;
@end
""");

var mainHeader = Path.Combine (tmpdir, "main.h");
File.WriteAllText (mainHeader, $"#import \"{scopedHeader}\"\n#import \"{unscopedHeader}\"\n");

binder.SplitDocuments = false;
binder.SourceFile = mainHeader;
binder.OutputDirectory = tmpdir;
binder.DirectoriesInScope.Add (scopedDir);
Configuration.IgnoreIfIgnoredPlatform (binder.Platform);
binder.PlatformAssembly = Extensions.GetPlatformAssemblyPath (binder.Platform);
binder.ClangResourceDirectory = Extensions.GetClangResourceDirectory ();
var bindings = binder.BindInOrOut ();

// Only ScopedClass should be in the output, not UnscopedClass
var expectedBindings =
"""
using Foundation;

// @interface ScopedClass
interface ScopedClass {
// @property int A;
[Export ("A")]
int A { get; set; }
}

""";
bindings.AssertSuccess (expectedBindings);
}

[Test]
public void Scope_PrefixDoesNotFalseMatch ()
{
// Verify that a scope of "/foo/bar" does not match "/foo/barbaz/header.h"
// (the scope must be a proper directory prefix with separator).
var binder = new BindTool ();
var tmpdir = Cache.CreateTemporaryDirectory ();
var scopedDir = Path.Combine (tmpdir, "scope");
var falseMatchDir = Path.Combine (tmpdir, "scopeextra");
Directory.CreateDirectory (scopedDir);
Directory.CreateDirectory (falseMatchDir);

var scopedHeader = Path.Combine (scopedDir, "Good.h");
File.WriteAllText (scopedHeader,
"""
@interface GoodClass {
}
@property int X;
@end
""");

var falseMatchHeader = Path.Combine (falseMatchDir, "Bad.h");
File.WriteAllText (falseMatchHeader,
"""
@interface BadClass {
}
@property int Y;
@end
""");

var mainHeader = Path.Combine (tmpdir, "main.h");
File.WriteAllText (mainHeader, $"#import \"{scopedHeader}\"\n#import \"{falseMatchHeader}\"\n");

binder.SplitDocuments = false;
binder.SourceFile = mainHeader;
binder.OutputDirectory = tmpdir;
binder.DirectoriesInScope.Add (scopedDir);
Configuration.IgnoreIfIgnoredPlatform (binder.Platform);
binder.PlatformAssembly = Extensions.GetPlatformAssemblyPath (binder.Platform);
binder.ClangResourceDirectory = Extensions.GetClangResourceDirectory ();
var bindings = binder.BindInOrOut ();

// Only GoodClass should appear (from "scope/"), not BadClass (from "scopeextra/")
var expectedBindings =
"""
using Foundation;

// @interface GoodClass
interface GoodClass {
// @property int X;
[Export ("X")]
int X { get; set; }
}

""";
bindings.AssertSuccess (expectedBindings);
}
}
7 changes: 6 additions & 1 deletion tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ bool IsInScope (Decl? decl)
if (string.IsNullOrEmpty (fn))
return true;
foreach (var dir in DirectoriesInScope) {
if (fn.StartsWith (dir, StringComparison.Ordinal))
// Ensure the scope directory ends with a directory separator so that
// a scope of "/foo/bar" doesn't falsely match "/foo/barbaz/header.h".
var normalizedDir = dir.EndsWith (Path.DirectorySeparatorChar) || dir.EndsWith (Path.AltDirectorySeparatorChar)
? dir
: dir + Path.DirectorySeparatorChar;
if (fn.StartsWith (normalizedDir, StringComparison.Ordinal))
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion tools/sharpie/Sharpie.Bind/Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static int Bind (string [] arguments)
{ "s|sdk=", "Target SDK.", v => binder.Sdk = v },
{ "f|framework=", "The input framework to bind. Implies setting the scope (--scope) to the framework, setting the namespace (--namespace) to the name of the framework, and no other sources/headers can be specified. If the framework provides an 'Info.plist' with SDK information (DTSDKName), the '-sdk' option will be implied as well (if not manually specified).", v => binder.SourceFramework = v },
{ "header=", "The input header file to bind. This can also be a .framework directory.", v => binder.SourceFile = v },
{ "scope=", "Restrict following #include and #import directives declared in header files to within the specified DIR directory.", v => binder.DirectoriesInScope.Add (v) },
{ "scope=", "Restrict following #include and #import directives declared in header files to within the specified DIR directory.", v => binder.DirectoriesInScope.Add (Path.GetFullPath (v)) },
{ "c|clang", "All arguments after this argument are not processed by Objective Sharpie and are proxied directly to Clang.", v => { } },
{ "clang-resource-dir=", "Specify the Clang resource directory.", v => binder.ClangResourceDirectory = v },
{ "platform-assembly=", "Specify the platform assembly to use for binding.", v => binder.PlatformAssembly = v },
Expand Down
Loading