-
Notifications
You must be signed in to change notification settings - Fork 53
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
[java-interop] Windows build system support #816
[java-interop] Windows build system support #816
Conversation
Context: 5c756b1 Context: 08ff4db With the `cmake`-based build system knowledge behind 5c756b1 in mind, update `src/java-interop` to *also* use a `cmake`-based build system, then add support so it can build on Windows. This allows the `Java.Interop-Tests.dll` unit tests to *also* be run on Windows, under both .NET Core and .NET Framework. Note that this requires updating `java_interop_lib_load()` to also include [`LOAD_LIBRARY_SEARCH_SYSTEM32`][0]: * `LOAD_LIBRARY_SEARCH_SYSTEM32`: `%windows%\system32` is searched for the DLL and its dependencies. This specifically is needed so that `JVM.DLL` dependencies such as `KERNEL32.DLL` can be resolved, allowing `JVM.DLL` to be loaded. This allows .NET Core on Windows to run `Java.Interop-Tests.dll`! > dotnet test bin\TestDebug-netcoreapp3.1\Java.Interop-Tests.dll … A total of 1 test files matched the specified pattern. Skipped Dispose_ClearsLocalReferences [11 ms] Passed! - Failed: 0, Passed: 617, Skipped: 1, Total: 618, Duration: 2 s - Java.Interop-Tests.dll (netcoreapp3.1) [0]: https://docs.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
#include "java-interop-logger.h" | ||
|
||
#define LOG_VA_ARGS(_kind_,_category_,_format_) \ | ||
do { \ | ||
const char* kind = (_kind_); \ | ||
LogCategories category = (_category_); \ | ||
va_list args; \ | ||
va_start (args, (_format_)); \ | ||
va_start (args, _format_); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Weird", yet required; without it, the MSVC compiler complains, because it considers (_format_)
to be a C++ reference type, via decltype(…)
. This was the easiest way to bring sanity.
Will it work?
Commit 6ede47a asked: > Will it work? Lol, no. No it does not: SetUp failed for test fixture Java.InteropTests.TestTypeTests System.TypeInitializationException : The type initializer for 'Java.InteropTests.JavaVMFixture' threw an exception. ----> System.BadImageFormatException : An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B) at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at NUnit.Framework.Internal.Reflect.Construct(Type type, Object[] arguments) at NUnit.Framework.Internal.TypeWrapper.Construct(Object[] args) at NUnit.Framework.Internal.Commands.ConstructFixtureCommand.<.ctor>b__0_0(TestExecutionContext context) at NUnit.Framework.Internal.Commands.BeforeTestCommand.Execute(TestExecutionContext context) at NUnit.Framework.Internal.Execution.CompositeWorkItem.PerformOneTimeSetUp() --BadImageFormatException at Java.Interop.NativeMethods.java_interop_jvm_load_with_error_message(String path, IntPtr& message) at Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions builder) in D:\a\1\s\src\Java.Runtime.Environment\Java.Interop\JreRuntime.cs:line 97 at Java.InteropTests.TestJVM..ctor(String[] jars, Dictionary`2 typeMappings) in D:\a\1\s\tests\TestJVM\TestJVM.cs:line 61 at Java.InteropTests.JavaVMFixture.CreateJavaVM() in D:\a\1\s\tests\Java.Interop-Tests\Java.Interop\JavaVMFixture.cs:line 15 at Java.InteropTests.JavaVMFixture..cctor() in D:\a\1\s\tests\Java.Interop-Tests\Java.Interop\JavaVMFixture.Partial.cs:line 12 …which is rather odd, actually; *why* `BadImageFormatException`? A plausible reason is that the "bitness" doesn't match, e.g. `dotnet` is 32-bit while `java-interop.dll` is 64-bit (or vice versa). The easiest way to tell is to download the built artifacts to see what they actually contain. Add a `PublishPipelineArtifact@` invocation so that we *can* download.
What's "interesting" is that if I download the new > dotnet test TestRelease-netcoreapp3.1\Java.Interop-Tests.dll
…
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Skipped Dispose_ClearsLocalReferences [11 ms]
Passed! - Failed: 0, Passed: 617, Skipped: 1, Total: 618, Duration: 2 s - Java.Interop-Tests.dll (netcoreapp3.1) Furthermore, % file TestRelease-netcoreapp3.1/java-interop.dll
TestRelease-netcoreapp3.1/java-interop.dll: PE32+ executable (DLL) (GUI) x86-64, for MS Windows More carefully reading the execution logs, it's running Which raises the next question: how do I force use of a 64-bit |
Also, is `dumpbin` in %PATH% on this machine?
Context: https://discord.com/channels/732297728826277939/732297952676020316/819978878948868166 Context: microsoft/vstest#2571 Looks like an updated version of `dotnet test` *might* fix my "test architecture" conundrum, so let's try using .NET 5.0.103!
For future reference, in order for
We need to use .NET 5.0.103. Earlier versions of .NET Core run our unit tests in a 32-bit process, which insta-fails, as we (currently) only build Alternatively, we need to figure out how to build |
A problem since 6ede47a is that 32-bit and 64-bit execution environments (1) are A Thing, and (2) won't be going away. Thus building `java-interop.dll` for only one architecture isn't a scalable solution. "Embrace" this, and rework the `src/java-interop` build system to run `cmake` *separately* for 32-bit and 64-bit libraries.
This reverts commit 08348d3. We know the answer; we were!
This reverts commit c14ebb6.
This reverts commit 6babd68. `dotnet.exe` was a 64-bit binary.
This reverts commit 0969a84. Setting TargetPlatform didn't help.
Commit f4d1109 changed cmake targets. Fixity fix.
It built locally! :-/
…why isn't it present?! "For good measure", append `$(PrepareNativeToolchain)` with `&& echo ABI && `, not just `&&`. (Note extra command *plus* trailing space.)
Something to mention for It's still desirable to show that CI can run |
This reverts commit 300f1c4. The "Why" is that it was NativeTiming that had the issue. Doh.
This reverts commit e134baf. Things are now working; we don't need to publish the `bin` dir.
All hail target batching? ALSO: If using `@(None)`/etc. into a sub-directory, as `src/java-interop` does for e.g. `$(OutputPath)win-x64\java-interop.dll`, you MUST: 1. Use `%(Link) to have the *relative* sub-dir + filename, AND 2. You MUST use `\` for the sub-dir! With these two incantations, `<Copy/>` will *ignore* any errors about the source files not existing, IFF the source & destination file paths are IDENTICAL. (String-wise identical!) Thus if you use `\`, you're 'fine' -- strings compare -- but if you use `/`, you're in for pain and suffering.
affae8c
to
cc2ef8b
Compare
<PrepareNativeToolchain>call "$(_Vcvarsall)" </PrepareNativeToolchain> | ||
</PropertyGroup> | ||
<PropertyGroup> | ||
<CmakeGenerator Condition=" '$(OS)' != 'Windows_NT' ">-G "Unix Makefiles"</CmakeGenerator> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can use Ninja, it tends to be faster
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alas, attempting to use ninja
broke on the macOS CI. :-(
I realize it's probably not caused by this PR, per-se, but this adds 48 C/C++ warnings to the build. Is it possible to fix or suppress them? Otherwise, LGTM. |
<_SourceDir>$(CmakeSourceDir.Replace('%5c', '/'))</_SourceDir> | ||
<_BuildDir>$(CmakeBuildDir.Replace('%5c', '/'))</_BuildDir> | ||
<_ExtraArgs>$(CmakeExtraArgs.Replace('%5c', '/'))</_ExtraArgs> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is already working, this is fine probably fine. But was the main issue here the <Exec/>
task?
Maybe it would work to make a small <UnixExec/>
task that subclasses <Exec/>
and replaces all the \
with /
in Command
? Then base.Execute()
would succeed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonathanpeppers : the problem isn't "unix", the problem is double quotes. :-)
"General guidelines", at least on Unix, is to quote anything which could possibly have a space…such as path names! The problem is when:
- The "path name" is a directory name, and
- The directory name ends with
\
.
The result is that you escape the quote!
$(_Prepare) $(CmakePath) $(CmakeGenerator) -S "$(_SourceDir)"
Here, $(_SourceDir)
will be e.g. $(MSBuildThisFileDirectory)
, which aways ends with \
. If we didn't "fix" $(_SourceDir)
, we'd have:
make -S "C:\some\directory\" …
The attempt to "sanely" quote the directory name results in an invalid command line.
Does this mean that s,\,/,g
is "best"? No; we could instead do:
<_SourceDir>$(CmakeSourceDir.TrimEnd('%5c'))</_SourceDir>
But! If a value can be "part of" the Cmake files, e.g. Cmake variables, those values must use /
, not \
, because Cmake otherwise attempts to treat \
as an escape character. Thus, $(CmakeExtraArgs)
must replace \
with /
, as it contains Cmake property overrides that contain paths.
In neither case does a <UnixExec/>
class help, unless a "command global" s,\,/,g
is desired. Note however that we don't "fixup" $(PrepareNativeToolchain)
or $(CmakePath)
.
<_JavaInteropLibName Condition=" '$(OS)' != 'Windows_NT' And Exists ('/Library/Frameworks/') ">libjava-interop.dylib</_JavaInteropLibName> | ||
<_JavaInteropLibName Condition=" '$(OS)' != 'Windows_NT' And !Exists ('/Library/Frameworks/') ">libjava-interop.so</_JavaInteropLibName> | ||
<_JavaInteropLibName Condition=" '$(OS)' == 'Windows_NT' ">java-interop.dll</_JavaInteropLibName> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a lot of existing code using $(OS)
, but I think the proper check is to use these:
$([MSBuild]::IsOSPlatform ('osx'))
$([MSBuild]::IsOSPlatform ('windows'))
$([MSBuild]::IsOSPlatform ('linux'))
I've seen DevCom issues where someone had %OS%
set to Windows
on their Dell PC!
<ItemGroup Condition=" '$(OS)' == 'Windows_NT' "> | ||
<_NativeTimingLib Include="CMakeLists.txt"> | ||
<Arch>x86_amd64</Arch> | ||
<Dir>win-x64\</Dir> | ||
</_NativeTimingLib> | ||
<_NativeTimingLib Include="CMakeLists.txt"> | ||
<Arch>x86</Arch> | ||
<Dir>win-x86\</Dir> | ||
</_NativeTimingLib> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition=" '$(OS)' != 'Windows_NT' "> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More $(OS)
checks.
Meanwhile:
So! Suppose you want an "output file" which is in a subdirectory of If you try the "obvious" thing: <ItemGroup>
<Content Include="$(OutputPath)win-x64\java-interop.dll" />
</ItemGroup> This fails:
It fails before any tarts which could create the specified file have a chance to execute. To make it work, two things must be done:
Thus: <!-- Good -->
<ItemGroup>
<Content Include="$(OutputPath)win-x64\java-interop.dll">
<Link>win-x64\java-interop.dll</Link>
</Content>
</ItemGroup> This works. It works because:
This is why <!-- Bad! -->
<ItemGroup>
<Content Include="$(OutputPath)win-x64/java-interop.dll">
<Link>win-x64/java-interop.dll</Link>
</Content>
</ItemGroup> it will fail, because
(I guess something is normalizing TL;DR: Avoid subdirectories within |
This reverts commit eb4eab1. Context: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=4563922&view=logs&jobId=2df166ff-203c-5833-cfba-e6fc8650c634&j=2df166ff-203c-5833-cfba-e6fc8650c634&t=8b9d5ac8-ce15-5110-c9f4-439f0fc78aa2 Looks like the macOS CI bot doesn't have `ninja` in `$PATH`: _RunCmake: cmake -G "Ninja" -S "/Users/runner/work/1/s/src/java-interop/" -B "/Users/runner/work/1/s/src/java-interop/obj/Release-netcoreapp3.1/" "-DJDK_INCLUDE_LIST=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/include;/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/include/darwin" "-DJNI_C_PATH=/Users/runner/work/1/s/src/java-interop/obj/Release-netcoreapp3.1/jni.c" && cmake --build "/Users/runner/work/1/s/src/java-interop/obj/Release-netcoreapp3.1/" -v -- Configuring incomplete, errors occurred! See also "/Users/runner/work/1/s/src/java-interop/obj/Release-netcoreapp3.1/CMakeFiles/CMakeOutput.log". EXEC : CMake warning : CMake was unable to find a build program corresponding to "Ninja". CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool. [/Users/runner/work/1/s/build-tools/scripts/RunCmake.proj] Revert the attempt to use Ninja.
Fix C4996 wrt `strdup`, by just `#define`ing it to `_strdup`. Fix C4700: java-interop-logger.cc(88,0): Warning C4700: uninitialized local variable 'category' used This was due to macro expansion, and the Microsoft C++ compiler didn't like a variable referring to a variable in a parent scope: log_error (LogCategories category, const char *format, ...) { do { LogCategories category = category; // C4700 ... } } Fix this by renaming the variable declared in the macro. Fix `Directory.Build.targets` so that changes to `*.cc` files cause things to be rebuilt.
Context: dotnet/msbuild#539 (comment) Sometimes the `%OS%` environment variable is set, which overrides the normally expected `$(OS)` MSBuild property. Instead of using `$(OS)`, use `$([MSBuild]::IsOSPlatform(name))`.
Context: 5c756b1
Context: 08ff4db
With the
cmake
-based build system knowledge behind 5c756b1 inmind, update
src/java-interop
to also use acmake
-based buildsystem, then add support so it can build on Windows.
This allows the
Java.Interop-Tests.dll
unit tests to also be runon Windows, under both .NET Core and .NET Framework.
Note that this requires updating
java_interop_lib_load()
to alsoinclude
LOAD_LIBRARY_SEARCH_SYSTEM32
:LOAD_LIBRARY_SEARCH_SYSTEM32
:%windows%\system32
is searchedfor the DLL and its dependencies.
This specifically is needed so that
JVM.DLL
dependencies such asKERNEL32.DLL
can be resolved, allowingJVM.DLL
to be loaded.This allows .NET Core on Windows to run
Java.Interop-Tests.dll
!