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

Question: copy bundled libvips to another directory #20

Closed
Gigas002 opened this issue Nov 6, 2018 · 18 comments
Closed

Question: copy bundled libvips to another directory #20

Gigas002 opened this issue Nov 6, 2018 · 18 comments
Labels
enhancement New feature or request

Comments

@Gigas002
Copy link

Gigas002 commented Nov 6, 2018

Is there possibility to set the directory for bundled libvips before runtime? I don't want to have all the dll's in my working directory, I'd prefer something like "Release/vips/".

<UseGlobalLibvips>true</UseGlobalLibvips>
<LibvipsDLLPath>C:\vips-dev-w64-web\bin</LibvipsDLLPath>

This just copies the vips in selected directory to the output directory. I can't set EnvironmentVariable this way:

private static void SetVipsPath()
{
    string executingAssemblyFile = Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("file:///", null);
    string executingDirectory = Path.GetDirectoryName(executingAssemblyFile);
    string vipsPath = $"{Path.Combine(executingDirectory, "vips")};";
    Environment.SetEnvironmentVariable("PATH", vipsPath);
}

either, because libvips's Initializer is called before runtime and throws exception because can't find libvips in my output directory.

Thanks in advance for your reply!

@kleisauke kleisauke added the question Further information is requested label Nov 6, 2018
@kleisauke
Copy link
Owner

Hi there,

See here for a detailed instruction to set the libvips binary directory at runtime. The bundled libvips that NetVips uses are taken from this repo.

I could add another property (for e.g. LibvipsDLLOutputPath) to indicate where the bundled libvips should be copied to. But this would lead to confusion because it's still necessary to add this directory to the PATH environment variable or to use the P/Invoke SetDllDirectory trick above.

Also, copying libvips and their dependencies to a subdirectory by default would break globally installed libvips versions, so I don't intend to do that.

@Gigas002
Copy link
Author

Gigas002 commented Nov 6, 2018

Hi there,

See here for a detailed instruction to set the libvips binary directory at runtime. The bundled libvips that NetVips uses are taken from this repo.

I could add another property (for e.g. LibvipsDLLOutputPath) to indicate where the bundled libvips should be copied to. But this would lead to confusion because it's still necessary to add this directory to the PATH environment variable or to use the P/Invoke SetDllDirectory trick above.

Also, copying libvips and their dependencies to a subdirectory by default would break globally installed libvips versions, so I don't intend to do that.

Thanks, that workaround really helped me. The only question I have now is why don't use main libvips repo? Bindings won't work correctly if I'll reference this version?

@kleisauke
Copy link
Owner

The binaries from the main libvips repo also work. The advantage of the build-win64-mxe distribution is that all dependencies are updated to the latest version (where possible), so it'll reduce the attack surface.

Hopefully this distribution will be incorporated in the official libvips binaries (see libvips/build-win64#22).

@Gigas002
Copy link
Author

Gigas002 commented Nov 6, 2018

Understood, thank you!

@Gigas002 Gigas002 closed this as completed Nov 6, 2018
@Gigas002
Copy link
Author

Gigas002 commented Nov 9, 2018

@kleisauke How about copying libvips in directory and set path variables like gdal.net does (it creates GdalConfigure class with ConfigureGdal() method which sets path variables for copied binaries, but I suppose netvips calls initializer automatically)? This won’t create global path variables (these exist only for current process and I’m pretty sure that you won’t use installed vips from the code if you’re using bindings, here’s docs on this method from msdn) so it won’t be problem for already installed on pc vips.

@kleisauke
Copy link
Owner

Modifying the DLL search path within a library feels clunky to me. In order to not bloat the project's output directory, I thought about an alternative:

  • Do not throw exceptions in the ModuleInitializer if the libvips DLL could not be found.
  • Let a user set the property LibvipsOutputBase to specify the subdirectory (within the project's output directory) where the libvips binaries are copied to.

When setting the LibvipsOutputBase a user must handle the libvips initializing logic himself (just call Base.VipsInit() with the correct environment). For example:

if (!ModuleInitializer.VipsInitialized)
{
    // Get the directory for the executing assembly in which the current code resides.
    var currentDirectory =
        Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

    // <LibvipsOutputBase>vips</LibvipsOutputBase>
    var vipsPath = Path.Combine(currentDirectory, "vips");

    // Prepend the vips path to PATH environment variable, to ensure the right libs are being used.
    var path = Environment.GetEnvironmentVariable("PATH");
    path = vipsPath + ";" + path;
    Environment.SetEnvironmentVariable("PATH", path);

    // Try to reinitialize libvips
    Base.VipsInit();
}

If you want to test this, you can use the nightly version of NetVips. Add the https://ci.appveyor.com/nuget/net-vips feed in the <packageSources> section of your NuGet.config:

<packageSources>
  <add key="netvips-nightly" value="https://ci.appveyor.com/nuget/net-vips" />
</packageSources>

And update NetVips to 1.0.6.102.

@kleisauke kleisauke added enhancement New feature or request and removed question Further information is requested labels Nov 16, 2018
@Gigas002
Copy link
Author

@kleisauke I've run some tests with both nighty version of package and rebuilt 1.0.6 branch. Everything seems to work correctly. I've also noticed, that setting LibvipsOutputBase doesn't take any effect. It can have any value or not exist in .csproj file at all. Seems like it's enought to just set EnvironmentVariable from code. I also tested on another pc without libvips installed on it, works correctly too.
I'd also add some exception handling in example code, like:

// Try to reinitialize libvips
try
{
    Base.VipsInit();
}
catch (Exception exception)
{
    switch (exception)
    {
        case DllNotFoundException _:
            Console.WriteLine("libvips dlls not found. Please, specify correct path.");
            break;
        case BadImageFormatException _:
            Console.WriteLine("libvips is unable to load. Try target x64.");
            break;
    }
}

BTW, how about automatically adding the VipsConfiguration class with static ConfigureVips() method (that contains code from the example and returns bool) with package to solution, or that would be an excess? I think, people would shortly notice it after updating package in VS without need to read release notes (or if someone is too lazy for it).

kleisauke added a commit that referenced this issue Nov 17, 2018
`LinkBase` property is only for .NET Core >= 2.0. See #20.
@kleisauke
Copy link
Owner

I've fixed the LibvipsOutputBase property with the above commit. If you want to test this, update the nightly version of NetVips to 1.0.6.103.

The only configuration that is needed is to call Base.VipsInit(), which will be automatically called by ModuleInitializer.Initialize once the assembly is loaded. So adding a VipsConfiguration with a static ConfigureVips function is superfluous and will cause confusion because the configuration is done automatically.

@Gigas002
Copy link
Author

Weird, but LibvipsOutputBase still doesn't seem to have any effect. How should it work?
I ran through the following tests:

<LibvipsOutputBase>vips</LibvipsOutputBase> OR empty OR not exists in .csproj;
vipsPath = Path.Combine(currentDirectory, "~vips");
actual vips directory name "~vips";
Result: Vips found.

And of course, if I change vipsPath to "vips" without renaming the directory and left <LibvipsOutputBase> unchanged, vips won't be found.

@kleisauke
Copy link
Owner

@Gigas002 My apologies for the slow reply; I've been busy with my internship.

LibvipsOutputBase doesn't work when UseGlobalLibvips is set to true, maybe this option is still enabled?

Also, NetVips tries to guess the target platform from the $(Platform) variable, perhaps this value might be something else than AnyCPU, x64 or x86. Did you see NetVips uses a bundled libvips x64-binary in your build output?

Otherwise, I'm not sure why it doesn't work for you. I've successfully tested this on .NET Core 2.1 and .NET Framework 4.5.

@Gigas002
Copy link
Author

Gigas002 commented Dec 2, 2018

@kleisauke Ah, my fault, I overlooked that UseGlobalLibvips was set to true. Run through some tests without it, and it works perfectly.

@kleisauke
Copy link
Owner

Thanks for testing! I'll publish a new version (1.0.6) on NuGet with this improvement in the course of next week.

@kleisauke
Copy link
Owner

The new version is delayed due to a packaging bug on Windows x86. I'll try to publish a new version on NuGet after this has been fixed (and libvips 8.7.3 is released). Sorry for the delay.

@Gigas002
Copy link
Author

The new version is delayed due to a packaging bug on Windows x86. I'll try to publish a new version on NuGet after this has been fixed (and libvips 8.7.3 is released). Sorry for the delay.

Sure, no problem!

@kleisauke
Copy link
Owner

NetVips 1.0.6 is now available on NuGet.

@Gigas002
Copy link
Author

@kleisauke Noticed something weird when referencing the project with NetVips from another project.
Let's say we have project1 which is library and project2, which reference that library.

If I try to use <LibvipsOutputBase>vips</LibvipsOutputBase> in both project1 and project2 and initialize NetVips in project2, then System.DllNotFoundException is thrown by Base.VipsInit() method. Same happens if I try to initialize NetVips in project1. If I remove <LibvipsOutputBase>vips</LibvipsOutputBase> everything works fine, but of course NetVips bindings are not in vips directory.

If I remove NetVips dependency in project2 and try to initialize it in project1, the exception is still thrown. Am I doing something wrong?

That also gave me a thought (Inspired by GDAL once again. Sorry, if it's a bad idea since 1.0.6 is already released) - probably it'll be better to create two separate Nuget packages: NetVips (which contains only NetVips.dll) and something like NetVips.Native (which contains original libvips bindings)?

@kleisauke
Copy link
Owner

I agree, the pre-compiled libvips binaries should be in a seperate package (for e.g. NetVips.Native.Windows, NetVips.Native.OSX and NetVips.Native.Linux).

I'll try to address this issue (along with #21) next month.

@kleisauke
Copy link
Owner

See: #21 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants