Skip to content

Commit 3824b97

Browse files
authored
[java-interop] Windows build system support (#816)
Context: 5c756b1 Context: 08ff4db Context: https://discord.com/channels/732297728826277939/732297952676020316/819978878948868166 Context: microsoft/vstest#2571 Context: dotnet/msbuild#539 (comment) Context: #816 (comment) 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. On Windows, this builds both e.g. a 32-bit `bin\Debug\win-x86\java-interop.dll` *and* 64-bit `bin\Debug\win-x64\java-interop.dll` libraries. This allows the `Java.Interop-Tests.dll` unit tests to *also* be run on Windows, under .NET Core [^0]. 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) Install .NET 5.0.103, as that version is required in order for `dotnet test Java.Interop-Tests.dll` to reliably use a 64-bit process. ~~~ Aside: So you want to create a file which is built into a *subdirectory* of `$(OutputPath)`. The "obvious thing" fails: <!-- Fails --> <ItemGroup> <None Include="$(OutputPath)win-x64\java-interop.dll" /> </ItemGroup> This results in: error MSB3030: Could not copy the file… This can be done by: 1. Providing `%(Link)` item metadata which contains the relative directory and filename, *not* `$(OutputPath)`, and 2. Using `\` *everywhere*. Thus: <!-- Works --> <ItemGroup> <None Include="$(OutputPath)win-x64\java-interop.dll"> <Link>win-x64\java-interop.dll</Link> </None> </ItemGroup> This works because: 1. The [`<AppendTargetPath/> task`][1] uses `%(Link)` as an *override* when setting `%(TargetPath)`. 2. The [`_CopyOutOfDateSourceItemsToOutputDirectory` target][2] uses `$(OutDir)%(TargetPath)` for `Copy.DestinationFiles`.. 3. The [`<Copy/>` task][3] *ignores errors* when `SourceFiles[i]` and `DestinationFiles[i]` are *identical*. This is why `\` must be used, to ensure that the values are identical. [^0]: …but not .NET Framework, as .NET Framework on CI is launching a 32-bit process for the unit tests, while we need a 64-bit process to match the `JVM.DLL` we're loading. [0]: https://docs.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw [1]: https://github.com/dotnet/msbuild/blob/17677364d656a98ce4c6eff73b49eddf24f0fd72/src/Tasks/AssignTargetPath.cs#L74 [2]: https://github.com/dotnet/msbuild/blob/17677364d656a98ce4c6eff73b49eddf24f0fd72/src/Tasks/Microsoft.Common.CurrentVersion.targets#L4965-L4977 [3]: https://github.com/dotnet/msbuild/blob/17677364d656a98ce4c6eff73b49eddf24f0fd72/src/Tasks/Copy.cs#L787-L796
1 parent 94c0c70 commit 3824b97

16 files changed

+365
-259
lines changed

build-tools/automation/azure-pipelines.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pr:
1818
variables:
1919
RunningOnCI: true
2020
Build.Configuration: Release
21-
DotNetCoreVersion: 3.1.300
21+
DotNetCoreVersion: 5.0.103
2222
HostedMacImage: macOS-10.15
2323
HostedWinVS2019: Hosted Windows 2019 with VS2019
2424
NetCoreTargetFrameworkPathSuffix: -netcoreapp3.1
@@ -90,6 +90,8 @@ jobs:
9090
- template: templates\core-build.yaml
9191

9292
- template: templates\core-tests.yaml
93+
parameters:
94+
runNativeDotnetTests: true
9395

9496
- template: templates\fail-on-issue.yaml
9597

build-tools/automation/templates/core-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ steps:
6969

7070
- task: DotNetCoreCLI@2
7171
displayName: 'Tests: Java.Interop'
72-
condition: eq('${{ parameters.runNativeTests }}', 'true')
72+
condition: or(eq('${{ parameters.runNativeDotnetTests }}', 'true'), eq('${{ parameters.runNativeTests }}', 'true'))
7373
inputs:
7474
command: test
7575
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-Tests.dll
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project>
3+
<Target Name="GetNativeBuildCommands">
4+
<ItemGroup Condition=" '$(VSINSTALLROOT)' != '' And Exists('$(VSINSTALLROOT)') ">
5+
<_Vcvarsall
6+
Include="$(VSINSTALLROOT)\VC\Auxiliary\Build\vcvarsall.bat"
7+
/>
8+
</ItemGroup>
9+
<PropertyGroup Condition=" '@(_Vcvarsall->Count())' != '0' ">
10+
<_Vcvarsall>%(_Vcvarsall.Identity)</_Vcvarsall>
11+
<PrepareNativeToolchain>call "$(_Vcvarsall)" </PrepareNativeToolchain>
12+
</PropertyGroup>
13+
<PropertyGroup>
14+
<CmakeGenerator Condition=" $([MSBuild]::IsOSPlatform ('windows')) ">-G "NMake Makefiles"</CmakeGenerator>
15+
<CmakeGenerator Condition=" !$([MSBuild]::IsOSPlatform ('windows')) ">-G "Unix Makefiles"</CmakeGenerator>
16+
</PropertyGroup>
17+
</Target>
18+
</Project>

build-tools/scripts/RunCmake.proj

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project DefaultTargets="Prepare" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Target Name="Cmake"
4+
DependsOnTargets="_ValidateCmake;_RunCmake"
5+
/>
6+
<Target Name="_ValidateCmake">
7+
<Error
8+
Condition=" '$(CmakePath)' == '' "
9+
Text="Set the `%24(CmakePath)` property."
10+
/>
11+
<Error
12+
Condition=" '$(CmakeGenerator)' == '' "
13+
Text="Set the `%24(CmakeGenerator)` property."
14+
/>
15+
<Error
16+
Condition=" '$(CmakeSourceDir)' == '' "
17+
Text="Set the `%24(CmakeSourceDir)` property."
18+
/>
19+
<Error
20+
Condition=" '$(CmakeBuildDir)' == '' "
21+
Text="Set the `%24(CmakeBuildDir)` property."
22+
/>
23+
</Target>
24+
25+
<Target Name="_RunCmake">
26+
<PropertyGroup>
27+
<_Prepare>$(PrepareNativeToolchain)</_Prepare>
28+
<_Prepare Condition=" '$(_Prepare)' != '' And !$(_Prepare.Trim().EndsWith('&amp;&amp;')) ">$(_Prepare) &amp;&amp;</_Prepare>
29+
<_SourceDir>$(CmakeSourceDir.Replace('%5c', '/'))</_SourceDir>
30+
<_BuildDir>$(CmakeBuildDir.Replace('%5c', '/'))</_BuildDir>
31+
<_ExtraArgs>$(CmakeExtraArgs.Replace('%5c', '/'))</_ExtraArgs>
32+
</PropertyGroup>
33+
<Exec
34+
ContinueOnError="WarnAndContinue"
35+
Command="$(_Prepare) $(CmakePath) $(CmakeGenerator) -S &quot;$(_SourceDir)&quot; -B &quot;$(_BuildDir)&quot; $(_ExtraArgs) &amp;&amp; $(CmakePath) --build &quot;$(_BuildDir)&quot; -v"
36+
/>
37+
<PropertyGroup>
38+
<_CmakeStatus>$(MSBuildLastTaskResult)</_CmakeStatus>
39+
</PropertyGroup>
40+
<ReadLinesFromFile
41+
Condition=" '$(_CmakeStatus)' == 'false' "
42+
File="$(CmakeBuildDir)CMakeFiles/CMakeOutput.log">
43+
<Output TaskParameter="Lines" ItemName="_CmakeLog" />
44+
</ReadLinesFromFile>
45+
<Message
46+
Condition=" '$(_CmakeStatus)' == 'false' "
47+
Text="CMakeOutput.log"
48+
/>
49+
<Message
50+
Condition=" '$(_CmakeStatus)' == 'false' "
51+
Text="@(_CmakeLog, '
52+
')"
53+
/>
54+
<ReadLinesFromFile
55+
Condition=" '$(_CmakeStatus)' == 'false' "
56+
File="$(CmakeBuildDir)CMakeFiles/CMakeError.log">
57+
<Output TaskParameter="Lines" ItemName="_CmakeErrorLog" />
58+
</ReadLinesFromFile>
59+
<Message
60+
Condition=" '$(_CmakeStatus)' == 'false' "
61+
Text="CMakeError.log"
62+
/>
63+
<Message
64+
Condition=" '$(_CmakeStatus)' == 'false' "
65+
Text="@(_CmakeErrorLog, '
66+
')"
67+
/>
68+
<Error
69+
Condition=" '$(_CmakeStatus)' == 'false' "
70+
Text="`cmake` failed. See previous messages."
71+
/>
72+
</Target>
73+
</Project>

src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ public JreRuntime CreateJreVM ()
7070

7171
public class JreRuntime : JniRuntime
7272
{
73+
static JreRuntime ()
74+
{
75+
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
76+
var baseDir = Path.GetDirectoryName (typeof (JreRuntime).Assembly.Location);
77+
var newDir = Path.Combine (baseDir, Environment.Is64BitProcess ? "win-x64" : "win-x86");
78+
NativeMethods.AddDllDirectory (newDir);
79+
}
80+
}
81+
7382
static int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args)
7483
{
7584
return NativeMethods.java_interop_jvm_create (out javavm, out jnienv, ref args);
@@ -168,6 +177,9 @@ partial class NativeMethods {
168177

169178
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
170179
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
180+
181+
[DllImport ("kernel32", CharSet=CharSet.Unicode)]
182+
internal static extern int AddDllDirectory (string NewDirectory);
171183
}
172184
}
173185

src/java-interop/CMakeLists.txt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
set(CMAKE_OSX_ARCHITECTURES x86_64 arm64)
2+
3+
project(
4+
java-interop
5+
DESCRIPTION "Java.Interop native support"
6+
HOMEPAGE_URL "https://github.com/xamarin/java.interop/"
7+
LANGUAGES CXX C
8+
)
9+
10+
set(CMAKE_C_STANDARD 11)
11+
set(CMAKE_CXX_STANDARD 17)
12+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
13+
set(CMAKE_CXX_EXTENSIONS OFF)
14+
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
15+
16+
option(ENABLE_MONO_INTEGRATION "Require Mono runtime" OFF)
17+
18+
cmake_minimum_required(VERSION 3.10.2)
19+
20+
set(JAVA_INTEROP_CORE_SOURCES
21+
java-interop-dlfcn.cc
22+
java-interop-jvm.cc
23+
java-interop-logger.cc
24+
java-interop-util.cc
25+
java-interop.cc
26+
${JNI_C_PATH}
27+
)
28+
set(JAVA_INTEROP_MONO_SOURCES
29+
java-interop-gc-bridge-mono.cc
30+
java-interop-mono.cc
31+
)
32+
33+
add_compile_definitions("JAVA_INTEROP_DLL_EXPORT")
34+
add_compile_definitions("JI_DLL_EXPORT")
35+
36+
foreach(dir in ${JDK_INCLUDE_LIST})
37+
include_directories(${dir})
38+
endforeach()
39+
40+
set(LINK_FLAGS "")
41+
42+
if(ENABLE_MONO_INTEGRATION)
43+
foreach(dir in ${MONO_INCLUDE_LIST})
44+
include_directories(${dir})
45+
endforeach()
46+
list(APPEND LINK_FLAGS ${MONO_LINK_FLAGS})
47+
list(APPEND LINK_FLAGS "-Wl,-undefined -Wl,suppress -Wl,-flat_namespace")
48+
set(JAVA_INTEROP_SOURCES ${JAVA_INTEROP_CORE_SOURCES} ${JAVA_INTEROP_MONO_SOURCES})
49+
else()
50+
set(JAVA_INTEROP_SOURCES ${JAVA_INTEROP_CORE_SOURCES})
51+
endif()
52+
53+
add_library(
54+
java-interop
55+
SHARED
56+
${JAVA_INTEROP_SOURCES}
57+
)
58+
target_link_libraries(
59+
java-interop
60+
${LINK_FLAGS}
61+
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<Project>
2+
3+
<Import Project="..\..\build-tools\scripts\NativeToolchain.targets" />
4+
5+
<PropertyGroup>
6+
<_JavaInteropLibName Condition=" $([MSBuild]::IsOSPlatform ('osx')) ">libjava-interop.dylib</_JavaInteropLibName>
7+
<_JavaInteropLibName Condition=" $([MSBuild]::IsOSPlatform ('linux')) ">libjava-interop.so</_JavaInteropLibName>
8+
<_JavaInteropLibName Condition=" $([MSBuild]::IsOSPlatform ('windows')) ">java-interop.dll</_JavaInteropLibName>
9+
</PropertyGroup>
10+
11+
<ItemGroup Condition=" $([MSBuild]::IsOSPlatform ('windows')) ">
12+
<_JavaInteropNativeLib Include="CMakeLists.txt">
13+
<Arch>x86_amd64</Arch>
14+
<Dir>win-x64\</Dir>
15+
</_JavaInteropNativeLib>
16+
<_JavaInteropNativeLib Include="CMakeLists.txt">
17+
<Arch>x86</Arch>
18+
<Dir>win-x86\</Dir>
19+
</_JavaInteropNativeLib>
20+
</ItemGroup>
21+
22+
<ItemGroup Condition=" !$([MSBuild]::IsOSPlatform ('windows')) ">
23+
<_JavaInteropNativeLib Include="CMakeLists.txt" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<None Include="@(_JavaInteropNativeLib->'$(OutputPath)%(Dir)$(_JavaInteropLibName)')">
28+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
29+
<Link>%(Dir)$(_JavaInteropLibName)</Link>
30+
</None>
31+
</ItemGroup>
32+
33+
<ItemGroup>
34+
<ClInclude Include="*.h" />
35+
</ItemGroup>
36+
37+
<ItemGroup>
38+
<ClCompile Include="$(IntermediateOutputPath)jni.c" />
39+
<ClCompile Include="*.cc" />
40+
</ItemGroup>
41+
42+
<Target Name="_BuildJni_c"
43+
Inputs="$(_JNIEnvGenPath)"
44+
Outputs="$(IntermediateOutputPath)jni.c">
45+
<MakeDir Directories="$(OutputPath)" />
46+
<Exec Command="$(_RunJNIEnvGen) $(IntermediateOutputPath)jni.g.cs $(IntermediateOutputPath)jni.c" />
47+
</Target>
48+
49+
<Target Name="_GetCmakeOptions">
50+
<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' And '@(MonoIncludePath->Count())' != '0' ">
51+
<_MonoDirs>"-DMONO_INCLUDE_LIST=@(MonoIncludePath, ';')"</_MonoDirs>
52+
<_MonoLib>"-DMONO_LINK_FLAGS=$(MonoLibs)"</_MonoLib>
53+
<_EnableMono>-DENABLE_MONO_INTEGRATION=ON</_EnableMono>
54+
</PropertyGroup>
55+
<PropertyGroup>
56+
<_JdkDirs>"-DJDK_INCLUDE_LIST=@(JdkIncludePath, ';')"</_JdkDirs>
57+
<_Jni_c>"-DJNI_C_PATH=$(MSBuildThisFileDirectory)$(IntermediateOutputPath)jni.c"</_Jni_c>
58+
<_ExtraArgs>$([MSBuild]::Escape('$(_JdkDirs) $(_Jni_c) $(_EnableMono) $(_MonoDirs) $(_MonoLib)'))</_ExtraArgs>
59+
</PropertyGroup>
60+
</Target>
61+
62+
<Target Name="_BuildLibs"
63+
DependsOnTargets="GetNativeBuildCommands;_BuildJni_c;_GetCmakeOptions"
64+
BeforeTargets="Build"
65+
Inputs="@(_JavaInteropNativeLib);$(MSBuildThisFileFullPath);java-interop.csproj;@(ClInclude);@(ClCompile)"
66+
Outputs="$(OutputPath)%(_JavaInteropNativeLib.Dir)$(_JavaInteropLibName)">
67+
<MakeDir Directories="$(IntermediateOutputPath)%(_JavaInteropNativeLib.Dir)" />
68+
<ItemGroup>
69+
<_Cmake
70+
Condition=" '$(PrepareNativeToolchain)' != '' "
71+
Include="PrepareNativeToolchain=$(PrepareNativeToolchain) %(_JavaInteropNativeLib.Arch)"
72+
/>
73+
<_Cmake Include="CmakePath=$(CmakePath)" />
74+
<_Cmake Include="CmakeGenerator=$(CmakeGenerator)" />
75+
<_Cmake Include="CmakeSourceDir=$(MSBuildThisFileDirectory)" />
76+
<_Cmake Include="CmakeBuildDir=$(MSBuildThisFileDirectory)$(IntermediateOutputPath)%(_JavaInteropNativeLib.Dir)" />
77+
<_Cmake Include="CmakeExtraArgs=$(_ExtraArgs)" />
78+
</ItemGroup>
79+
<MSBuild
80+
Projects="..\..\build-tools\scripts\RunCmake.proj"
81+
Properties="@(_Cmake)"
82+
Targets="Cmake"
83+
/>
84+
<MakeDir Directories="$(OutputPath)%(_JavaInteropNativeLib.Dir)" />
85+
<ItemGroup>
86+
<_Libs Include="$(IntermediateOutputPath)%(_JavaInteropNativeLib.Dir)$(_JavaInteropLibName)*" />
87+
</ItemGroup>
88+
<Copy
89+
SourceFiles="@(_Libs)"
90+
DestinationFolder="$(OutputPath)%(_JavaInteropNativeLib.Dir)"
91+
/>
92+
<Touch Files="$(OutputPath)%(_JavaInteropNativeLib.Dir)$(_JavaInteropLibName)" />
93+
</Target>
94+
95+
<Target Name="_Clean"
96+
AfterTargets="Clean">
97+
<Delete Files="@(None)" />
98+
</Target>
99+
100+
</Project>

0 commit comments

Comments
 (0)